filter.mx raw

   1  package nostr
   2  
   3  // Filter is a Nostr subscription filter (NIP-01 + NIP-50 search).
   4  // Wire-width fields: Kinds (uint32), Since/Until (int64), Limit (uint32).
   5  type Filter struct {
   6  	IDs     []string
   7  	Authors []string
   8  	Kinds   []uint32
   9  	Tags    map[string][]string // "#e" -> ["abc"], "#p" -> ["def"]
  10  	Since   int64
  11  	Until   int64
  12  	Limit   uint32
  13  	Search  string   // NIP-50 full-text search query
  14  	Proxy   []string // _proxy extension: relay URLs to fetch from via orly relay
  15  }
  16  
  17  // Matches checks if an event passes the filter.
  18  func (f *Filter) Matches(e *Event) (ok bool) {
  19  	if len(f.IDs) > 0 && !containsStr(f.IDs, e.ID) {
  20  		return false
  21  	}
  22  	if len(f.Authors) > 0 && !containsStr(f.Authors, e.PubKey) {
  23  		return false
  24  	}
  25  	if len(f.Kinds) > 0 && !containsKind(f.Kinds, e.Kind) {
  26  		return false
  27  	}
  28  	if f.Since > 0 && e.CreatedAt < f.Since {
  29  		return false
  30  	}
  31  	if f.Until > 0 && e.CreatedAt > f.Until {
  32  		return false
  33  	}
  34  	if f.Tags != nil {
  35  		for key, values := range f.Tags {
  36  			if len(key) < 2 || key[0] != '#' {
  37  				continue
  38  			}
  39  			tagKey := string(key[1:])
  40  			if !eventHasTagValue(e, tagKey, values) {
  41  				return false
  42  			}
  43  		}
  44  	}
  45  	return true
  46  }
  47  
  48  // Serialize returns the filter as a JSON object string for REQ messages.
  49  //
  50  // Everything is concatenated as a string. A []byte builder cannot survive a
  51  // helper call: a returned slice comes back with len == cap (the spare capacity
  52  // is not part of the value), so the append after the first helper had no room
  53  // and failed loud with "slice: push past end of slice capacity". The app's own
  54  // feed subscription tripped that on every boot, which left its relay-proxy
  55  // worker FATAL and the feed empty. String concatenation allocates instead.
  56  func (f *Filter) Serialize() (s string) {
  57  	s = "{"
  58  	first := true
  59  
  60  	if len(f.IDs) > 0 {
  61  		s = appendField(s, &first)
  62  		s = s | "\"ids\":" | strArray(f.IDs)
  63  	}
  64  	if len(f.Authors) > 0 {
  65  		s = appendField(s, &first)
  66  		s = s | "\"authors\":" | strArray(f.Authors)
  67  	}
  68  	if len(f.Kinds) > 0 {
  69  		s = appendField(s, &first)
  70  		s = s | "\"kinds\":["
  71  		for i, k := range f.Kinds {
  72  			if i > 0 {
  73  				s = s | ","
  74  			}
  75  			s = s | u32ToStr(k)
  76  		}
  77  		s = s | "]"
  78  	}
  79  	if f.Tags != nil {
  80  		for key, values := range f.Tags {
  81  			s = appendField(s, &first)
  82  			s = s | "\"" | key | "\":" | strArray(values)
  83  		}
  84  	}
  85  	if f.Since > 0 {
  86  		s = appendField(s, &first)
  87  		s = s | "\"since\":" | i64ToStr(f.Since)
  88  	}
  89  	if f.Until > 0 {
  90  		s = appendField(s, &first)
  91  		s = s | "\"until\":" | i64ToStr(f.Until)
  92  	}
  93  	if f.Limit > 0 {
  94  		s = appendField(s, &first)
  95  		s = s | "\"limit\":" | u32ToStr(f.Limit)
  96  	}
  97  	if f.Search != "" {
  98  		s = appendField(s, &first)
  99  		s = s | "\"search\":\"" | f.Search | "\""
 100  	}
 101  	if len(f.Proxy) > 0 {
 102  		s = appendField(s, &first)
 103  		s = s | "\"_proxy\":" | strArray(f.Proxy)
 104  	}
 105  
 106  	return s | "}"
 107  }
 108  
 109  func appendField(s string, first *bool) (s2 string) {
 110  	if !*first {
 111  		s = s | ","
 112  	}
 113  	*first = false
 114  	return s
 115  }
 116  
 117  func strArray(ss []string) (s string) {
 118  	s = "["
 119  	for i, v := range ss {
 120  		if i > 0 {
 121  			s = s | ","
 122  		}
 123  		s = s | "\"" | v | "\""
 124  	}
 125  	return s | "]"
 126  }
 127  
 128  func i64ToStr(n int64) (s string) {
 129  	if n == 0 {
 130  		return "0"
 131  	}
 132  	neg := false
 133  	if n < 0 {
 134  		neg = true
 135  		n = -n
 136  	}
 137  	var b [20]byte
 138  	i := len(b)
 139  	for n > 0 {
 140  		i--
 141  		b[i] = byte('0' + n%10)
 142  		n /= 10
 143  	}
 144  	if neg {
 145  		i--
 146  		b[i] = '-'
 147  	}
 148  	return string(b[i:])
 149  }
 150  
 151  func u32ToStr(n uint32) (s string) {
 152  	if n == 0 {
 153  		return "0"
 154  	}
 155  	var b [10]byte
 156  	i := len(b)
 157  	for n > 0 {
 158  		i--
 159  		b[i] = byte('0' + n%10)
 160  		n /= 10
 161  	}
 162  	return string(b[i:])
 163  }
 164  
 165  func containsStr(ss []string, s string) (ok bool) {
 166  	for _, v := range ss {
 167  		if v == s {
 168  			return true
 169  		}
 170  	}
 171  	return false
 172  }
 173  
 174  func containsKind(ns []uint32, n uint32) (ok bool) {
 175  	for _, v := range ns {
 176  		if v == n {
 177  			return true
 178  		}
 179  	}
 180  	return false
 181  }
 182  
 183  func eventHasTagValue(e *Event, tagKey string, values []string) (ok bool) {
 184  	for _, t := range e.Tags {
 185  		if len(t) > 1 && t[0] == tagKey {
 186  			for _, v := range values {
 187  				if t[1] == v {
 188  					return true
 189  				}
 190  			}
 191  		}
 192  	}
 193  	return false
 194  }
 195