package filter import ( "bytes" "crypto/sha256" "sort" "smesh.lol/pkg/nostr/ec/schnorr" "smesh.lol/pkg/nostr/event" "smesh.lol/pkg/nostr/ints" "smesh.lol/pkg/nostr/kind" "smesh.lol/pkg/nostr/tag" "smesh.lol/pkg/nostr/text" "smesh.lol/pkg/nostr/timestamp" "smesh.lol/pkg/lol/chk" "smesh.lol/pkg/lol/errorf" ) type F struct { Ids *tag.T `json:"ids,omitempty"` Kinds *kind.S `json:"kinds,omitempty"` Authors *tag.T `json:"authors,omitempty"` Tags *tag.S `json:"-,omitempty"` Since *timestamp.T `json:"since,omitempty"` Until *timestamp.T `json:"until,omitempty"` Search []byte `json:"search,omitempty"` Limit *uint `json:"limit,omitempty"` Extra map[string][]byte `json:"-"` } func New() (f *F) { return &F{ Ids: tag.NewWithCap(10), Kinds: kind.NewWithCap(10), Authors: tag.NewWithCap(10), Tags: tag.NewSWithCap(10), Since: timestamp.New(), Until: timestamp.New(), } } var ( IDs = []byte("ids") Kinds = []byte("kinds") Authors = []byte("authors") Since = []byte("since") Until = []byte("until") Limit = []byte("limit") Search = []byte("search") ) func (f *F) Sort() { if f.Ids != nil { sort.Sort(f.Ids) } if f.Kinds != nil { sort.Sort(f.Kinds) } if f.Authors != nil { sort.Sort(f.Authors) } if f.Tags != nil { for i, v := range *f.Tags { if len(v.T) > 2 { // Insertion sort on tag values (v.T[1:]). for ii := 2; ii < len(v.T); ii++ { for jj := ii; jj > 1 && bytes.Compare(v.T[jj], v.T[jj-1]) < 0; jj-- { v.T[jj], v.T[jj-1] = v.T[jj-1], v.T[jj] } } (*f.Tags)[i] = v } } sort.Sort(f.Tags) } } func (f *F) MatchesIgnoringTimestampConstraints(ev *event.E) bool { if ev == nil { return false } if f.Ids.Len() > 0 && !f.Ids.Contains(ev.ID) { return false } if f.Kinds.Len() > 0 && !f.Kinds.Contains(ev.Kind) { return false } if f.Authors.Len() > 0 { found := false for _, author := range f.Authors.T { if bytes.Equal(author, ev.Pubkey) { found = true break } } if !found { return false } } if f.Tags.Len() > 0 { for _, v := range *f.Tags { if v.Len() < 2 { continue } key := v.Key() values := v.T[1:] if !ev.Tags.ContainsAny(key, values) { return false } } } return true } func (f *F) Matches(ev *event.E) (match bool) { if !f.MatchesIgnoringTimestampConstraints(ev) { return } if f.Since.Int() != 0 && ev.CreatedAt < f.Since.I64() { return } if f.Until.Int() != 0 && ev.CreatedAt > f.Until.I64() { return } return true } func (f *F) Marshal(dst []byte) (b []byte) { var first bool if dst == nil { dst = []byte{:0:256} } f.Sort() b = dst b = append(b, '{') if f.Ids != nil && f.Ids.Len() > 0 { first = true b = text.JSONKey(b, IDs) b = text.MarshalHexArray(b, f.Ids.T) } if f.Kinds.Len() > 0 { if first { b = append(b, ',') } else { first = true } b = text.JSONKey(b, Kinds) b = f.Kinds.Marshal(b) } if f.Authors.Len() > 0 { if first { b = append(b, ',') } else { first = true } b = text.JSONKey(b, Authors) b = text.MarshalHexArray(b, f.Authors.T) } if f.Tags != nil && f.Tags.Len() > 0 { for _, tg := range *f.Tags { if tg == nil || tg.Len() < 2 { continue } tKey := tg.T[0] if len(tKey) != 1 || ((tKey[0] < 'a' || tKey[0] > 'z') && (tKey[0] < 'A' || tKey[0] > 'Z')) { continue } values := tg.T[1:] if len(values) == 0 { continue } if first { b = append(b, ',') } else { first = true } b = append(b, '"', '#', tKey[0], '"', ':') b = append(b, '[') for i, value := range values { b = text.AppendQuote(b, value, text.NostrEscape) if i < len(values)-1 { b = append(b, ',') } } b = append(b, ']') } } if f.Since != nil && f.Since.U64() > 0 { if first { b = append(b, ',') } else { first = true } b = text.JSONKey(b, Since) b = f.Since.Marshal(b) } if f.Until != nil && f.Until.U64() > 0 { if first { b = append(b, ',') } else { first = true } b = text.JSONKey(b, Until) b = f.Until.Marshal(b) } if len(f.Search) > 0 { if first { b = append(b, ',') } else { first = true } b = text.JSONKey(b, Search) b = text.AppendQuote(b, f.Search, text.NostrEscape) } if f.Limit != nil { if first { b = append(b, ',') } b = text.JSONKey(b, Limit) b = ints.New(*f.Limit).Marshal(b) } b = append(b, '}') return } func (f *F) Serialize() (b []byte) { return f.Marshal(nil) } const ( beforeOpen = iota openParen inKey inKV inVal betweenKV afterClose ) func (f *F) Unmarshal(b []byte) (r []byte, err error) { r = b var key []byte var state int for ; len(r) > 0; r = r[1:] { switch state { case beforeOpen: if r[0] == '{' { state = openParen } case openParen: if r[0] == '"' { state = inKey } case inKey: if r[0] == '"' { state = inKV } else { if key == nil { key = []byte{:0:16} } key = append(key, r[0]) } case inKV: if r[0] == ':' { state = inVal } case inVal: if len(key) < 1 { err = errorf.E([]byte("filter key zero length: '%s'\n'%s"), b, r) return } switch key[0] { case '#': l := len(key) if l != 2 { err = errorf.E( []byte("filter tag keys can only be # and one alpha character: '%s'\n%s"), key, b, ) return } k := []byte{:1} k[0] = key[1] var ff [][]byte if ff, r, err = text.UnmarshalStringArray(r); chk.E(err) { return } ff = append([][]byte{k}, ff...) if f.Tags == nil { f.Tags = tag.NewSWithCap(1) } s := append(*f.Tags, tag.NewFromBytesSlice(ff...)) f.Tags = &s state = betweenKV case IDs[0]: if len(key) < len(IDs) { goto invalid } var ff [][]byte if ff, r, err = text.UnmarshalHexArray(r, sha256.Size); chk.E(err) { return } f.Ids = tag.NewFromBytesSlice(ff...) state = betweenKV case Kinds[0]: if len(key) < len(Kinds) { goto invalid } f.Kinds = kind.NewWithCap(0) if r, err = f.Kinds.Unmarshal(r); chk.E(err) { return } state = betweenKV case Authors[0]: if len(key) < len(Authors) { goto invalid } var ff [][]byte if ff, r, err = text.UnmarshalHexArray(r, schnorr.PubKeyBytesLen); chk.E(err) { return } f.Authors = tag.NewFromBytesSlice(ff...) state = betweenKV case Until[0]: if len(key) < len(Until) { goto invalid } u := ints.New(0) if r, err = u.Unmarshal(r); chk.E(err) { return } f.Until = timestamp.FromUnix(int64(u.N)) state = betweenKV case Limit[0]: if len(key) < len(Limit) { goto invalid } l := ints.New(0) if r, err = l.Unmarshal(r); chk.E(err) { return } u := uint(l.N) f.Limit = &u state = betweenKV case Search[0]: if len(key) < len(Since) { goto invalid } switch key[1] { case Search[1]: if len(key) < len(Search) { goto invalid } var txt []byte if txt, r, err = text.UnmarshalQuoted(r); chk.E(err) { return } f.Search = txt state = betweenKV case Since[1]: if len(key) < len(Since) { goto invalid } s := ints.New(0) if r, err = s.Unmarshal(r); chk.E(err) { return } f.Since = timestamp.FromUnix(int64(s.N)) state = betweenKV } default: var val []byte if val, r, err = skipJSONValue(r); err != nil { goto invalid } if f.Extra == nil { f.Extra = map[string][]byte{} } f.Extra[string(key)] = val state = betweenKV } key = key[:0] case betweenKV: if len(r) == 0 { return } if r[0] == '}' { state = afterClose } else if r[0] == ',' { state = openParen } else if r[0] == '"' { state = inKey } } if len(r) == 0 { return } if r[0] == '}' { r = r[1:] return } } invalid: err = errorf.E([]byte("invalid key,\n'%s'\n'%s'"), string(b), string(r)) return }