// Package crawler discovers Nostr relays by extracting relay URLs // from kind 10002 (relay list metadata) events stored locally. package crawler import ( "git.smesh.lol/moxie/pkg/mxutil" "git.smesh.lol/nostr/pkg/filter" "git.smesh.lol/nostr/pkg/kind" "git.smesh.lol/morly/pkg/store" ) // Crawler discovers relay URLs from the local store. type Crawler struct { store *store.Engine } // New creates a crawler. func New(s *store.Engine) (c *Crawler) { return &Crawler{store: s} } // Relay is a discovered relay with its usage hints. type Relay struct { URL string Read bool Write bool } // Discover scans the store for kind 10002 events and extracts relay URLs. // Returns deduplicated relays. func (c *Crawler) Discover() (rs []Relay) { kind.Ensure() f := &filter.F{ Kinds: kind.NewS(kind.RelayListMetadata), } events, err := c.store.QueryEvents(f) if err != nil { return nil } seen := map[string]*Relay{} for _, ev := range events { if ev.Tags == nil { continue } rTags := ev.Tags.GetAll([]byte("r")) for _, rt := range rTags { if rt.Len() < 2 { continue } url := string(rt.Value()) if url == "" { continue } r, ok := seen[url] if !ok { r = &Relay{URL: url} seen[url] = r } // Third element is optional: "read", "write", or absent (both). hint := rt.Relay() if len(hint) == 0 { r.Read = true r.Write = true } else if string(hint) == "read" { r.Read = true } else if string(hint) == "write" { r.Write = true } } } out := []Relay{:0:len(seen)} for _, r := range seen { out = mxutil.Ensure(out, 1) out = push(out, *r) } return out } // DiscoverURLs returns just the relay URL strings. func (c *Crawler) DiscoverURLs() (ss []string) { relays := c.Discover() urls := []string{:len(relays)} for i, r := range relays { urls[i] = r.URL } return urls }