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