summaryrefslogtreecommitdiffstats
path: root/filter.go
diff options
context:
space:
mode:
authorbndw <ben@bdw.to>2026-02-07 15:20:57 -0800
committerbndw <ben@bdw.to>2026-02-07 15:20:57 -0800
commitd4fd2467d691a69a0ba75348086424b9fb33a297 (patch)
tree51bae6f1579e3248843a01053ccdea336f2730b2 /filter.go
wip
Diffstat (limited to 'filter.go')
-rw-r--r--filter.go224
1 files changed, 224 insertions, 0 deletions
diff --git a/filter.go b/filter.go
new file mode 100644
index 0000000..dde04a5
--- /dev/null
+++ b/filter.go
@@ -0,0 +1,224 @@
1package nostr
2
3import (
4 "encoding/json"
5 "strings"
6)
7
8// Filter represents a subscription filter as defined in NIP-01.
9type Filter struct {
10 IDs []string `json:"ids,omitempty"`
11 Kinds []int `json:"kinds,omitempty"`
12 Authors []string `json:"authors,omitempty"`
13 Tags map[string][]string `json:"-"` // Custom marshaling for #e, #p, etc.
14 Since *int64 `json:"since,omitempty"`
15 Until *int64 `json:"until,omitempty"`
16 Limit int `json:"limit,omitempty"`
17}
18
19// MarshalJSON implements json.Marshaler for Filter.
20// Converts Tags map to #e, #p format.
21func (f Filter) MarshalJSON() ([]byte, error) {
22 // Create a map for custom marshaling
23 m := make(map[string]interface{})
24
25 if len(f.IDs) > 0 {
26 m["ids"] = f.IDs
27 }
28 if len(f.Kinds) > 0 {
29 m["kinds"] = f.Kinds
30 }
31 if len(f.Authors) > 0 {
32 m["authors"] = f.Authors
33 }
34 if f.Since != nil {
35 m["since"] = *f.Since
36 }
37 if f.Until != nil {
38 m["until"] = *f.Until
39 }
40 if f.Limit > 0 {
41 m["limit"] = f.Limit
42 }
43
44 // Add tag filters with # prefix
45 for key, values := range f.Tags {
46 if len(values) > 0 {
47 m["#"+key] = values
48 }
49 }
50
51 return json.Marshal(m)
52}
53
54// UnmarshalJSON implements json.Unmarshaler for Filter.
55// Extracts #e, #p format into Tags map.
56func (f *Filter) UnmarshalJSON(data []byte) error {
57 // First unmarshal into a raw map
58 var raw map[string]json.RawMessage
59 if err := json.Unmarshal(data, &raw); err != nil {
60 return err
61 }
62
63 // Extract known fields
64 if v, ok := raw["ids"]; ok {
65 if err := json.Unmarshal(v, &f.IDs); err != nil {
66 return err
67 }
68 }
69 if v, ok := raw["kinds"]; ok {
70 if err := json.Unmarshal(v, &f.Kinds); err != nil {
71 return err
72 }
73 }
74 if v, ok := raw["authors"]; ok {
75 if err := json.Unmarshal(v, &f.Authors); err != nil {
76 return err
77 }
78 }
79 if v, ok := raw["since"]; ok {
80 var since int64
81 if err := json.Unmarshal(v, &since); err != nil {
82 return err
83 }
84 f.Since = &since
85 }
86 if v, ok := raw["until"]; ok {
87 var until int64
88 if err := json.Unmarshal(v, &until); err != nil {
89 return err
90 }
91 f.Until = &until
92 }
93 if v, ok := raw["limit"]; ok {
94 if err := json.Unmarshal(v, &f.Limit); err != nil {
95 return err
96 }
97 }
98
99 // Extract tag filters (fields starting with #)
100 f.Tags = make(map[string][]string)
101 for key, value := range raw {
102 if strings.HasPrefix(key, "#") {
103 tagKey := strings.TrimPrefix(key, "#")
104 var values []string
105 if err := json.Unmarshal(value, &values); err != nil {
106 return err
107 }
108 f.Tags[tagKey] = values
109 }
110 }
111
112 return nil
113}
114
115// Matches checks if an event matches this filter.
116func (f *Filter) Matches(event *Event) bool {
117 // Check IDs (prefix match)
118 if len(f.IDs) > 0 {
119 found := false
120 for _, id := range f.IDs {
121 if strings.HasPrefix(event.ID, id) {
122 found = true
123 break
124 }
125 }
126 if !found {
127 return false
128 }
129 }
130
131 // Check authors (prefix match)
132 if len(f.Authors) > 0 {
133 found := false
134 for _, author := range f.Authors {
135 if strings.HasPrefix(event.PubKey, author) {
136 found = true
137 break
138 }
139 }
140 if !found {
141 return false
142 }
143 }
144
145 // Check kinds
146 if len(f.Kinds) > 0 {
147 found := false
148 for _, kind := range f.Kinds {
149 if event.Kind == kind {
150 found = true
151 break
152 }
153 }
154 if !found {
155 return false
156 }
157 }
158
159 // Check since
160 if f.Since != nil && event.CreatedAt < *f.Since {
161 return false
162 }
163
164 // Check until
165 if f.Until != nil && event.CreatedAt > *f.Until {
166 return false
167 }
168
169 // Check tag filters
170 for tagKey, values := range f.Tags {
171 if len(values) == 0 {
172 continue
173 }
174 found := false
175 for _, val := range values {
176 if event.Tags.ContainsValue(tagKey, val) {
177 found = true
178 break
179 }
180 }
181 if !found {
182 return false
183 }
184 }
185
186 return true
187}
188
189// Clone creates a deep copy of the filter.
190func (f *Filter) Clone() *Filter {
191 clone := &Filter{
192 Limit: f.Limit,
193 }
194
195 if f.IDs != nil {
196 clone.IDs = make([]string, len(f.IDs))
197 copy(clone.IDs, f.IDs)
198 }
199 if f.Kinds != nil {
200 clone.Kinds = make([]int, len(f.Kinds))
201 copy(clone.Kinds, f.Kinds)
202 }
203 if f.Authors != nil {
204 clone.Authors = make([]string, len(f.Authors))
205 copy(clone.Authors, f.Authors)
206 }
207 if f.Since != nil {
208 since := *f.Since
209 clone.Since = &since
210 }
211 if f.Until != nil {
212 until := *f.Until
213 clone.Until = &until
214 }
215 if f.Tags != nil {
216 clone.Tags = make(map[string][]string)
217 for k, v := range f.Tags {
218 clone.Tags[k] = make([]string, len(v))
219 copy(clone.Tags[k], v)
220 }
221 }
222
223 return clone
224}