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