diff options
| author | bndw <ben@bdw.to> | 2026-02-07 15:20:57 -0800 |
|---|---|---|
| committer | bndw <ben@bdw.to> | 2026-02-07 15:20:57 -0800 |
| commit | d4fd2467d691a69a0ba75348086424b9fb33a297 (patch) | |
| tree | 51bae6f1579e3248843a01053ccdea336f2730b2 /filter.go | |
wip
Diffstat (limited to 'filter.go')
| -rw-r--r-- | filter.go | 224 |
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 @@ | |||
| 1 | package nostr | ||
| 2 | |||
| 3 | import ( | ||
| 4 | "encoding/json" | ||
| 5 | "strings" | ||
| 6 | ) | ||
| 7 | |||
| 8 | // Filter represents a subscription filter as defined in NIP-01. | ||
| 9 | type 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. | ||
| 21 | func (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. | ||
| 56 | func (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. | ||
| 116 | func (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. | ||
| 190 | func (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 | } | ||
