filter.mx raw
1 package nostr
2
3 // Filter is a Nostr subscription filter (NIP-01).
4 type Filter struct {
5 IDs []string
6 Authors []string
7 Kinds []int
8 Tags map[string][]string // "#e" -> ["abc"], "#p" -> ["def"]
9 Since int64
10 Until int64
11 Limit int
12 Proxy []string // _proxy extension: relay URLs to fetch from via orly relay
13 }
14
15 // Matches checks if an event passes the filter.
16 func (f *Filter) Matches(e *Event) bool {
17 if len(f.IDs) > 0 && !containsStr(f.IDs, e.ID) {
18 return false
19 }
20 if len(f.Authors) > 0 && !containsStr(f.Authors, e.PubKey) {
21 return false
22 }
23 if len(f.Kinds) > 0 && !containsInt(f.Kinds, e.Kind) {
24 return false
25 }
26 if f.Since > 0 && e.CreatedAt < f.Since {
27 return false
28 }
29 if f.Until > 0 && e.CreatedAt > f.Until {
30 return false
31 }
32 if f.Tags != nil {
33 for key, values := range f.Tags {
34 if len(key) < 2 || key[0] != '#' {
35 continue
36 }
37 tagKey := string(key[1:])
38 if !eventHasTagValue(e, tagKey, values) {
39 return false
40 }
41 }
42 }
43 return true
44 }
45
46 // Serialize returns the filter as a JSON object string for REQ messages.
47 func (f *Filter) Serialize() string {
48 buf := []byte{:0:128}
49 buf = append(buf, '{')
50 first := true
51
52 if len(f.IDs) > 0 {
53 buf = appendField(buf, &first)
54 buf = append(buf, "\"ids\":"...)
55 buf = appendStrArray(buf, f.IDs)
56 }
57 if len(f.Authors) > 0 {
58 buf = appendField(buf, &first)
59 buf = append(buf, "\"authors\":"...)
60 buf = appendStrArray(buf, f.Authors)
61 }
62 if len(f.Kinds) > 0 {
63 buf = appendField(buf, &first)
64 buf = append(buf, "\"kinds\":["...)
65 for i, k := range f.Kinds {
66 if i > 0 {
67 buf = append(buf, ',')
68 }
69 buf = append(buf, intToStr(k)...)
70 }
71 buf = append(buf, ']')
72 }
73 if f.Tags != nil {
74 for key, values := range f.Tags {
75 buf = appendField(buf, &first)
76 buf = append(buf, '"')
77 buf = append(buf, key...)
78 buf = append(buf, "\":"...)
79 buf = appendStrArray(buf, values)
80 }
81 }
82 if f.Since > 0 {
83 buf = appendField(buf, &first)
84 buf = append(buf, "\"since\":"...)
85 buf = append(buf, i64ToStr(f.Since)...)
86 }
87 if f.Until > 0 {
88 buf = appendField(buf, &first)
89 buf = append(buf, "\"until\":"...)
90 buf = append(buf, i64ToStr(f.Until)...)
91 }
92 if f.Limit > 0 {
93 buf = appendField(buf, &first)
94 buf = append(buf, "\"limit\":"...)
95 buf = append(buf, intToStr(f.Limit)...)
96 }
97 if len(f.Proxy) > 0 {
98 buf = appendField(buf, &first)
99 buf = append(buf, "\"_proxy\":"...)
100 buf = appendStrArray(buf, f.Proxy)
101 }
102
103 buf = append(buf, '}')
104 return string(buf)
105 }
106
107 func appendField(buf []byte, first *bool) []byte {
108 if !*first {
109 buf = append(buf, ',')
110 }
111 *first = false
112 return buf
113 }
114
115 func appendStrArray(buf []byte, ss []string) []byte {
116 buf = append(buf, '[')
117 for i, s := range ss {
118 if i > 0 {
119 buf = append(buf, ',')
120 }
121 buf = append(buf, '"')
122 buf = append(buf, s...)
123 buf = append(buf, '"')
124 }
125 buf = append(buf, ']')
126 return buf
127 }
128
129 func i64ToStr(n int64) string {
130 if n == 0 {
131 return "0"
132 }
133 neg := false
134 if n < 0 {
135 neg = true
136 n = -n
137 }
138 var b [20]byte
139 i := len(b)
140 for n > 0 {
141 i--
142 b[i] = byte('0' + n%10)
143 n /= 10
144 }
145 if neg {
146 i--
147 b[i] = '-'
148 }
149 return string(b[i:])
150 }
151
152 func intToStr(n int) string {
153 if n == 0 {
154 return "0"
155 }
156 neg := false
157 if n < 0 {
158 neg = true
159 n = -n
160 }
161 var b [20]byte
162 i := len(b)
163 for n > 0 {
164 i--
165 b[i] = byte('0' + n%10)
166 n /= 10
167 }
168 if neg {
169 i--
170 b[i] = '-'
171 }
172 return string(b[i:])
173 }
174
175 func containsStr(ss []string, s string) bool {
176 for _, v := range ss {
177 if v == s {
178 return true
179 }
180 }
181 return false
182 }
183
184 func containsInt(ns []int, n int) bool {
185 for _, v := range ns {
186 if v == n {
187 return true
188 }
189 }
190 return false
191 }
192
193 func eventHasTagValue(e *Event, tagKey string, values []string) bool {
194 for _, t := range e.Tags {
195 if len(t) > 1 && t[0] == tagKey {
196 for _, v := range values {
197 if t[1] == v {
198 return true
199 }
200 }
201 }
202 }
203 return false
204 }
205