package nostr import ( "encoding/json" "strings" ) // Filter represents a subscription filter as defined in NIP-01. type Filter struct { IDs []string `json:"ids,omitempty"` Kinds []int `json:"kinds,omitempty"` Authors []string `json:"authors,omitempty"` Tags map[string][]string `json:"-"` // Custom marshaling for #e, #p, etc. Since *int64 `json:"since,omitempty"` Until *int64 `json:"until,omitempty"` Limit int `json:"limit,omitempty"` } // MarshalJSON implements json.Marshaler for Filter. // Converts Tags map to #e, #p format. func (f Filter) MarshalJSON() ([]byte, error) { // Create a map for custom marshaling m := make(map[string]interface{}) if len(f.IDs) > 0 { m["ids"] = f.IDs } if len(f.Kinds) > 0 { m["kinds"] = f.Kinds } if len(f.Authors) > 0 { m["authors"] = f.Authors } if f.Since != nil { m["since"] = *f.Since } if f.Until != nil { m["until"] = *f.Until } if f.Limit > 0 { m["limit"] = f.Limit } // Add tag filters with # prefix for key, values := range f.Tags { if len(values) > 0 { m["#"+key] = values } } return json.Marshal(m) } // UnmarshalJSON implements json.Unmarshaler for Filter. // Extracts #e, #p format into Tags map. func (f *Filter) UnmarshalJSON(data []byte) error { // First unmarshal into a raw map var raw map[string]json.RawMessage if err := json.Unmarshal(data, &raw); err != nil { return err } // Extract known fields if v, ok := raw["ids"]; ok { if err := json.Unmarshal(v, &f.IDs); err != nil { return err } } if v, ok := raw["kinds"]; ok { if err := json.Unmarshal(v, &f.Kinds); err != nil { return err } } if v, ok := raw["authors"]; ok { if err := json.Unmarshal(v, &f.Authors); err != nil { return err } } if v, ok := raw["since"]; ok { var since int64 if err := json.Unmarshal(v, &since); err != nil { return err } f.Since = &since } if v, ok := raw["until"]; ok { var until int64 if err := json.Unmarshal(v, &until); err != nil { return err } f.Until = &until } if v, ok := raw["limit"]; ok { if err := json.Unmarshal(v, &f.Limit); err != nil { return err } } // Extract tag filters (fields starting with #) f.Tags = make(map[string][]string) for key, value := range raw { if strings.HasPrefix(key, "#") { tagKey := strings.TrimPrefix(key, "#") var values []string if err := json.Unmarshal(value, &values); err != nil { return err } f.Tags[tagKey] = values } } return nil } // Matches checks if an event matches this filter. func (f *Filter) Matches(event *Event) bool { // Check IDs (prefix match) if len(f.IDs) > 0 { found := false for _, id := range f.IDs { if strings.HasPrefix(event.ID, id) { found = true break } } if !found { return false } } // Check authors (prefix match) if len(f.Authors) > 0 { found := false for _, author := range f.Authors { if strings.HasPrefix(event.PubKey, author) { found = true break } } if !found { return false } } // Check kinds if len(f.Kinds) > 0 { found := false for _, kind := range f.Kinds { if event.Kind == kind { found = true break } } if !found { return false } } // Check since if f.Since != nil && event.CreatedAt < *f.Since { return false } // Check until if f.Until != nil && event.CreatedAt > *f.Until { return false } // Check tag filters for tagKey, values := range f.Tags { if len(values) == 0 { continue } found := false for _, val := range values { if event.Tags.ContainsValue(tagKey, val) { found = true break } } if !found { return false } } return true } // Clone creates a deep copy of the filter. func (f *Filter) Clone() *Filter { clone := &Filter{ Limit: f.Limit, } if f.IDs != nil { clone.IDs = make([]string, len(f.IDs)) copy(clone.IDs, f.IDs) } if f.Kinds != nil { clone.Kinds = make([]int, len(f.Kinds)) copy(clone.Kinds, f.Kinds) } if f.Authors != nil { clone.Authors = make([]string, len(f.Authors)) copy(clone.Authors, f.Authors) } if f.Since != nil { since := *f.Since clone.Since = &since } if f.Until != nil { until := *f.Until clone.Until = &until } if f.Tags != nil { clone.Tags = make(map[string][]string) for k, v := range f.Tags { clone.Tags[k] = make([]string, len(v)) copy(clone.Tags[k], v) } } return clone }