filter.mx raw

   1  package filter
   2  
   3  import (
   4  	"bytes"
   5  	"crypto/sha256"
   6  	"sort"
   7  
   8  	"smesh.lol/pkg/nostr/ec/schnorr"
   9  	"smesh.lol/pkg/nostr/event"
  10  	"smesh.lol/pkg/nostr/ints"
  11  	"smesh.lol/pkg/nostr/kind"
  12  	"smesh.lol/pkg/nostr/tag"
  13  	"smesh.lol/pkg/nostr/text"
  14  	"smesh.lol/pkg/nostr/timestamp"
  15  	"smesh.lol/pkg/lol/chk"
  16  	"smesh.lol/pkg/lol/errorf"
  17  )
  18  
  19  type F struct {
  20  	Ids     *tag.T       `json:"ids,omitempty"`
  21  	Kinds   *kind.S      `json:"kinds,omitempty"`
  22  	Authors *tag.T       `json:"authors,omitempty"`
  23  	Tags    *tag.S       `json:"-,omitempty"`
  24  	Since   *timestamp.T `json:"since,omitempty"`
  25  	Until   *timestamp.T `json:"until,omitempty"`
  26  	Search  []byte       `json:"search,omitempty"`
  27  	Limit   *uint        `json:"limit,omitempty"`
  28  	Extra   map[string][]byte `json:"-"`
  29  }
  30  
  31  func New() (f *F) {
  32  	return &F{
  33  		Ids:     tag.NewWithCap(10),
  34  		Kinds:   kind.NewWithCap(10),
  35  		Authors: tag.NewWithCap(10),
  36  		Tags:    tag.NewSWithCap(10),
  37  		Since:   timestamp.New(),
  38  		Until:   timestamp.New(),
  39  	}
  40  }
  41  
  42  var (
  43  	IDs     = []byte("ids")
  44  	Kinds   = []byte("kinds")
  45  	Authors = []byte("authors")
  46  	Since   = []byte("since")
  47  	Until   = []byte("until")
  48  	Limit   = []byte("limit")
  49  	Search  = []byte("search")
  50  )
  51  
  52  func (f *F) Sort() {
  53  	if f.Ids != nil {
  54  		sort.Sort(f.Ids)
  55  	}
  56  	if f.Kinds != nil {
  57  		sort.Sort(f.Kinds)
  58  	}
  59  	if f.Authors != nil {
  60  		sort.Sort(f.Authors)
  61  	}
  62  	if f.Tags != nil {
  63  		for i, v := range *f.Tags {
  64  			if len(v.T) > 2 {
  65  				// Insertion sort on tag values (v.T[1:]).
  66  				for ii := 2; ii < len(v.T); ii++ {
  67  					for jj := ii; jj > 1 && bytes.Compare(v.T[jj], v.T[jj-1]) < 0; jj-- {
  68  						v.T[jj], v.T[jj-1] = v.T[jj-1], v.T[jj]
  69  					}
  70  				}
  71  				(*f.Tags)[i] = v
  72  			}
  73  		}
  74  		sort.Sort(f.Tags)
  75  	}
  76  }
  77  
  78  func (f *F) MatchesIgnoringTimestampConstraints(ev *event.E) bool {
  79  	if ev == nil {
  80  		return false
  81  	}
  82  	if f.Ids.Len() > 0 && !f.Ids.Contains(ev.ID) {
  83  		return false
  84  	}
  85  	if f.Kinds.Len() > 0 && !f.Kinds.Contains(ev.Kind) {
  86  		return false
  87  	}
  88  	if f.Authors.Len() > 0 {
  89  		found := false
  90  		for _, author := range f.Authors.T {
  91  			if bytes.Equal(author, ev.Pubkey) {
  92  				found = true
  93  				break
  94  			}
  95  		}
  96  		if !found {
  97  			return false
  98  		}
  99  	}
 100  	if f.Tags.Len() > 0 {
 101  		for _, v := range *f.Tags {
 102  			if v.Len() < 2 {
 103  				continue
 104  			}
 105  			key := v.Key()
 106  			values := v.T[1:]
 107  			if !ev.Tags.ContainsAny(key, values) {
 108  				return false
 109  			}
 110  		}
 111  	}
 112  	return true
 113  }
 114  
 115  func (f *F) Matches(ev *event.E) (match bool) {
 116  	if !f.MatchesIgnoringTimestampConstraints(ev) {
 117  		return
 118  	}
 119  	if f.Since.Int() != 0 && ev.CreatedAt < f.Since.I64() {
 120  		return
 121  	}
 122  	if f.Until.Int() != 0 && ev.CreatedAt > f.Until.I64() {
 123  		return
 124  	}
 125  	return true
 126  }
 127  
 128  func (f *F) Marshal(dst []byte) (b []byte) {
 129  	var first bool
 130  	if dst == nil {
 131  		dst = []byte{:0:256}
 132  	}
 133  	f.Sort()
 134  	b = dst
 135  	b = append(b, '{')
 136  	if f.Ids != nil && f.Ids.Len() > 0 {
 137  		first = true
 138  		b = text.JSONKey(b, IDs)
 139  		b = text.MarshalHexArray(b, f.Ids.T)
 140  	}
 141  	if f.Kinds.Len() > 0 {
 142  		if first {
 143  			b = append(b, ',')
 144  		} else {
 145  			first = true
 146  		}
 147  		b = text.JSONKey(b, Kinds)
 148  		b = f.Kinds.Marshal(b)
 149  	}
 150  	if f.Authors.Len() > 0 {
 151  		if first {
 152  			b = append(b, ',')
 153  		} else {
 154  			first = true
 155  		}
 156  		b = text.JSONKey(b, Authors)
 157  		b = text.MarshalHexArray(b, f.Authors.T)
 158  	}
 159  	if f.Tags != nil && f.Tags.Len() > 0 {
 160  		for _, tg := range *f.Tags {
 161  			if tg == nil || tg.Len() < 2 {
 162  				continue
 163  			}
 164  			tKey := tg.T[0]
 165  			if len(tKey) != 1 ||
 166  				((tKey[0] < 'a' || tKey[0] > 'z') && (tKey[0] < 'A' || tKey[0] > 'Z')) {
 167  				continue
 168  			}
 169  			values := tg.T[1:]
 170  			if len(values) == 0 {
 171  				continue
 172  			}
 173  			if first {
 174  				b = append(b, ',')
 175  			} else {
 176  				first = true
 177  			}
 178  			b = append(b, '"', '#', tKey[0], '"', ':')
 179  			b = append(b, '[')
 180  			for i, value := range values {
 181  				b = text.AppendQuote(b, value, text.NostrEscape)
 182  				if i < len(values)-1 {
 183  					b = append(b, ',')
 184  				}
 185  			}
 186  			b = append(b, ']')
 187  		}
 188  	}
 189  	if f.Since != nil && f.Since.U64() > 0 {
 190  		if first {
 191  			b = append(b, ',')
 192  		} else {
 193  			first = true
 194  		}
 195  		b = text.JSONKey(b, Since)
 196  		b = f.Since.Marshal(b)
 197  	}
 198  	if f.Until != nil && f.Until.U64() > 0 {
 199  		if first {
 200  			b = append(b, ',')
 201  		} else {
 202  			first = true
 203  		}
 204  		b = text.JSONKey(b, Until)
 205  		b = f.Until.Marshal(b)
 206  	}
 207  	if len(f.Search) > 0 {
 208  		if first {
 209  			b = append(b, ',')
 210  		} else {
 211  			first = true
 212  		}
 213  		b = text.JSONKey(b, Search)
 214  		b = text.AppendQuote(b, f.Search, text.NostrEscape)
 215  	}
 216  	if f.Limit != nil {
 217  		if first {
 218  			b = append(b, ',')
 219  		}
 220  		b = text.JSONKey(b, Limit)
 221  		b = ints.New(*f.Limit).Marshal(b)
 222  	}
 223  	b = append(b, '}')
 224  	return
 225  }
 226  
 227  func (f *F) Serialize() (b []byte) { return f.Marshal(nil) }
 228  
 229  const (
 230  	beforeOpen = iota
 231  	openParen
 232  	inKey
 233  	inKV
 234  	inVal
 235  	betweenKV
 236  	afterClose
 237  )
 238  
 239  func (f *F) Unmarshal(b []byte) (r []byte, err error) {
 240  	r = b
 241  	var key []byte
 242  	var state int
 243  	for ; len(r) > 0; r = r[1:] {
 244  		switch state {
 245  		case beforeOpen:
 246  			if r[0] == '{' {
 247  				state = openParen
 248  			}
 249  		case openParen:
 250  			if r[0] == '"' {
 251  				state = inKey
 252  			}
 253  		case inKey:
 254  			if r[0] == '"' {
 255  				state = inKV
 256  			} else {
 257  				if key == nil {
 258  					key = []byte{:0:16}
 259  				}
 260  				key = append(key, r[0])
 261  			}
 262  		case inKV:
 263  			if r[0] == ':' {
 264  				state = inVal
 265  			}
 266  		case inVal:
 267  			if len(key) < 1 {
 268  				err = errorf.E([]byte("filter key zero length: '%s'\n'%s"), b, r)
 269  				return
 270  			}
 271  			switch key[0] {
 272  			case '#':
 273  				l := len(key)
 274  				if l != 2 {
 275  					err = errorf.E(
 276  						[]byte("filter tag keys can only be # and one alpha character: '%s'\n%s"),
 277  						key, b,
 278  					)
 279  					return
 280  				}
 281  				k := []byte{:1}
 282  				k[0] = key[1]
 283  				var ff [][]byte
 284  				if ff, r, err = text.UnmarshalStringArray(r); chk.E(err) {
 285  					return
 286  				}
 287  				ff = append([][]byte{k}, ff...)
 288  				if f.Tags == nil {
 289  					f.Tags = tag.NewSWithCap(1)
 290  				}
 291  				s := append(*f.Tags, tag.NewFromBytesSlice(ff...))
 292  				f.Tags = &s
 293  				state = betweenKV
 294  			case IDs[0]:
 295  				if len(key) < len(IDs) {
 296  					goto invalid
 297  				}
 298  				var ff [][]byte
 299  				if ff, r, err = text.UnmarshalHexArray(r, sha256.Size); chk.E(err) {
 300  					return
 301  				}
 302  				f.Ids = tag.NewFromBytesSlice(ff...)
 303  				state = betweenKV
 304  			case Kinds[0]:
 305  				if len(key) < len(Kinds) {
 306  					goto invalid
 307  				}
 308  				f.Kinds = kind.NewWithCap(0)
 309  				if r, err = f.Kinds.Unmarshal(r); chk.E(err) {
 310  					return
 311  				}
 312  				state = betweenKV
 313  			case Authors[0]:
 314  				if len(key) < len(Authors) {
 315  					goto invalid
 316  				}
 317  				var ff [][]byte
 318  				if ff, r, err = text.UnmarshalHexArray(r, schnorr.PubKeyBytesLen); chk.E(err) {
 319  					return
 320  				}
 321  				f.Authors = tag.NewFromBytesSlice(ff...)
 322  				state = betweenKV
 323  			case Until[0]:
 324  				if len(key) < len(Until) {
 325  					goto invalid
 326  				}
 327  				u := ints.New(0)
 328  				if r, err = u.Unmarshal(r); chk.E(err) {
 329  					return
 330  				}
 331  				f.Until = timestamp.FromUnix(int64(u.N))
 332  				state = betweenKV
 333  			case Limit[0]:
 334  				if len(key) < len(Limit) {
 335  					goto invalid
 336  				}
 337  				l := ints.New(0)
 338  				if r, err = l.Unmarshal(r); chk.E(err) {
 339  					return
 340  				}
 341  				u := uint(l.N)
 342  				f.Limit = &u
 343  				state = betweenKV
 344  			case Search[0]:
 345  				if len(key) < len(Since) {
 346  					goto invalid
 347  				}
 348  				switch key[1] {
 349  				case Search[1]:
 350  					if len(key) < len(Search) {
 351  						goto invalid
 352  					}
 353  					var txt []byte
 354  					if txt, r, err = text.UnmarshalQuoted(r); chk.E(err) {
 355  						return
 356  					}
 357  					f.Search = txt
 358  					state = betweenKV
 359  				case Since[1]:
 360  					if len(key) < len(Since) {
 361  						goto invalid
 362  					}
 363  					s := ints.New(0)
 364  					if r, err = s.Unmarshal(r); chk.E(err) {
 365  						return
 366  					}
 367  					f.Since = timestamp.FromUnix(int64(s.N))
 368  					state = betweenKV
 369  				}
 370  			default:
 371  				var val []byte
 372  				if val, r, err = skipJSONValue(r); err != nil {
 373  					goto invalid
 374  				}
 375  				if f.Extra == nil {
 376  					f.Extra = map[string][]byte{}
 377  				}
 378  				f.Extra[string(key)] = val
 379  				state = betweenKV
 380  			}
 381  			key = key[:0]
 382  		case betweenKV:
 383  			if len(r) == 0 {
 384  				return
 385  			}
 386  			if r[0] == '}' {
 387  				state = afterClose
 388  			} else if r[0] == ',' {
 389  				state = openParen
 390  			} else if r[0] == '"' {
 391  				state = inKey
 392  			}
 393  		}
 394  		if len(r) == 0 {
 395  			return
 396  		}
 397  		if r[0] == '}' {
 398  			r = r[1:]
 399  			return
 400  		}
 401  	}
 402  invalid:
 403  	err = errorf.E([]byte("invalid key,\n'%s'\n'%s'"), string(b), string(r))
 404  	return
 405  }
 406