// Package crawler discovers Nostr relays by extracting relay URLs // from kind 10002 (relay list metadata) events stored locally. package crawler import ( "smesh.lol/pkg/nostr/filter" "smesh.lol/pkg/nostr/kind" "smesh.lol/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) *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() []Relay { 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 = append(out, *r) } return out } // DiscoverURLs returns just the relay URL strings. func (c *Crawler) DiscoverURLs() []string { relays := c.Discover() urls := []string{:len(relays)} for i, r := range relays { urls[i] = r.URL } return urls }