crawler.mx raw

   1  // Package crawler discovers Nostr relays by extracting relay URLs
   2  // from kind 10002 (relay list metadata) events stored locally.
   3  package crawler
   4  
   5  import (
   6  	"git.smesh.lol/moxie/pkg/mxutil"
   7  	"git.smesh.lol/nostr/pkg/filter"
   8  	"git.smesh.lol/nostr/pkg/kind"
   9  	"git.smesh.lol/morly/pkg/store"
  10  )
  11  
  12  // Crawler discovers relay URLs from the local store.
  13  type Crawler struct {
  14  	store *store.Engine
  15  }
  16  
  17  // New creates a crawler.
  18  func New(s *store.Engine) (c *Crawler) { return &Crawler{store: s} }
  19  
  20  // Relay is a discovered relay with its usage hints.
  21  type Relay struct {
  22  	URL   string
  23  	Read  bool
  24  	Write bool
  25  }
  26  
  27  // Discover scans the store for kind 10002 events and extracts relay URLs.
  28  // Returns deduplicated relays.
  29  func (c *Crawler) Discover() (rs []Relay) {
  30  	kind.Ensure()
  31  	f := &filter.F{
  32  		Kinds: kind.NewS(kind.RelayListMetadata),
  33  	}
  34  	events, err := c.store.QueryEvents(f)
  35  	if err != nil {
  36  		return nil
  37  	}
  38  
  39  	seen := map[string]*Relay{}
  40  	for _, ev := range events {
  41  		if ev.Tags == nil {
  42  			continue
  43  		}
  44  		rTags := ev.Tags.GetAll([]byte("r"))
  45  		for _, rt := range rTags {
  46  			if rt.Len() < 2 {
  47  				continue
  48  			}
  49  			url := string(rt.Value())
  50  			if url == "" {
  51  				continue
  52  			}
  53  			r, ok := seen[url]
  54  			if !ok {
  55  				r = &Relay{URL: url}
  56  				seen[url] = r
  57  			}
  58  			// Third element is optional: "read", "write", or absent (both).
  59  			hint := rt.Relay()
  60  			if len(hint) == 0 {
  61  				r.Read = true
  62  				r.Write = true
  63  			} else if string(hint) == "read" {
  64  				r.Read = true
  65  			} else if string(hint) == "write" {
  66  				r.Write = true
  67  			}
  68  		}
  69  	}
  70  
  71  	out := []Relay{:0:len(seen)}
  72  	for _, r := range seen {
  73  		out = mxutil.Ensure(out, 1)
  74  		out = push(out, *r)
  75  	}
  76  	return out
  77  }
  78  
  79  // DiscoverURLs returns just the relay URL strings.
  80  func (c *Crawler) DiscoverURLs() (ss []string) {
  81  	relays := c.Discover()
  82  	urls := []string{:len(relays)}
  83  	for i, r := range relays {
  84  		urls[i] = r.URL
  85  	}
  86  	return urls
  87  }
  88