Skip to content
This repository was archived by the owner on Jan 24, 2026. It is now read-only.
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion envelopes_sonic.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ type sonicVisitor struct {
currentFilter *Filter
currentFilterTagList []string
currentFilterTagName string
currentFilterTagIsAnd bool

smp *sonicMessageParser
mainEnvelope Envelope
Expand Down Expand Up @@ -192,7 +193,11 @@ func (sv *sonicVisitor) OnArrayEnd() error {
sv.whereWeAre = inFilterObject
sv.smp.doneWithIntSlice(sv.currentFilter.Kinds)
case inAFilterTag:
sv.currentFilter.Tags[sv.currentFilterTagName] = sv.currentFilterTagList
if sv.currentFilterTagIsAnd {
sv.currentFilter.TagsAnd[sv.currentFilterTagName] = sv.currentFilterTagList
} else {
sv.currentFilter.Tags[sv.currentFilterTagName] = sv.currentFilterTagList
}
sv.whereWeAre = inFilterObject
sv.smp.doneWithStringSlice(sv.currentFilterTagList)

Expand Down Expand Up @@ -287,6 +292,15 @@ func (sv *sonicVisitor) OnObjectKey(key string) error {
}
sv.currentFilterTagList = sv.smp.reusableStringArray
sv.currentFilterTagName = key[1:]
sv.currentFilterTagIsAnd = false
sv.whereWeAre = inAFilterTag
} else if len(key) > 1 && key[0] == '&' {
if sv.currentFilter.TagsAnd == nil {
sv.currentFilter.TagsAnd = make(TagMap, 1)
}
sv.currentFilterTagList = sv.smp.reusableStringArray
sv.currentFilterTagName = key[1:]
sv.currentFilterTagIsAnd = true
sv.whereWeAre = inAFilterTag
} else {
return fmt.Errorf("unexpected filter attr %s", key)
Expand Down
47 changes: 45 additions & 2 deletions filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type Filter struct {
Kinds []int
Authors []string
Tags TagMap
TagsAnd TagMap
Since *Timestamp
Until *Timestamp
Limit int
Expand Down Expand Up @@ -85,12 +86,33 @@ func (ef Filter) MatchesIgnoringTimestampConstraints(event *Event) bool {
return false
}

for f, v := range ef.Tags {
if v != nil && !event.Tags.ContainsAny(f, v) {
// First check AND tags: all values must be present
for f, v := range ef.TagsAnd {
if v != nil && !event.Tags.ContainsAll(f, v) {
return false
}
}

// Then check OR tags: any value must be present, but exclude values that are in TagsAnd
for f, v := range ef.Tags {
if v != nil {
// Exclude values that are in TagsAnd for the same tag name
andValues := ef.TagsAnd[f]
filteredValues := make([]string, 0, len(v))
for _, val := range v {
if !slices.Contains(andValues, val) {
filteredValues = append(filteredValues, val)
}
}

// If there are no values left after filtering, skip this tag
// (all values were in TagsAnd, which was already checked)
if len(filteredValues) > 0 && !event.Tags.ContainsAny(f, filteredValues) {
return false
}
}
}

return true
}

Expand Down Expand Up @@ -121,6 +143,20 @@ func FilterEqual(a Filter, b Filter) bool {
}
}

if len(a.TagsAnd) != len(b.TagsAnd) {
return false
}

for f, av := range a.TagsAnd {
if bv, ok := b.TagsAnd[f]; !ok {
return false
} else {
if !similar(av, bv) {
return false
}
}
}

if !arePointerValuesEqual(a.Since, b.Since) {
return false
}
Expand Down Expand Up @@ -157,6 +193,13 @@ func (ef Filter) Clone() Filter {
}
}

if ef.TagsAnd != nil {
clone.TagsAnd = make(TagMap, len(ef.TagsAnd))
for k, v := range ef.TagsAnd {
clone.TagsAnd[k] = slices.Clone(v)
}
}

if ef.Since != nil {
since := *ef.Since
clone.Since = &since
Expand Down
42 changes: 42 additions & 0 deletions filter_easyjson.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ func easyjson4d398eaaDecodeGithubComNbdWtfGoNostr(in *jlexer.Lexer, out *Filter)
return
}
out.Tags = make(TagMap)
out.TagsAnd = make(TagMap)
in.Delim('{')
for !in.IsDelim('}') {
key := in.UnsafeFieldName(false)
Expand Down Expand Up @@ -152,6 +153,28 @@ func easyjson4d398eaaDecodeGithubComNbdWtfGoNostr(in *jlexer.Lexer, out *Filter)
in.Delim(']')
}
out.Tags[key[1:]] = tagValues
} else if len(key) > 1 && key[0] == '&' {
tagValues := make([]string, 0, 40)
if !in.IsNull() {
in.Delim('[')
if out.Authors == nil {
if !in.IsDelim(']') {
tagValues = make([]string, 0, 4)
} else {
tagValues = []string{}
}
} else {
tagValues = (tagValues)[:0]
}
for !in.IsDelim(']') {
var v3 string
v3 = string(in.String())
tagValues = append(tagValues, v3)
in.WantComma()
}
in.Delim(']')
}
out.TagsAnd[key[1:]] = tagValues
} else {
in.SkipRecursive()
}
Expand Down Expand Up @@ -280,6 +303,25 @@ func easyjson4d398eaaEncodeGithubComNbdWtfGoNostr(out *jwriter.Writer, in Filter
out.RawByte(']')
}
}
for tag, values := range in.TagsAnd {
const prefix string = ",\"authors\":"
if first {
first = false
out.RawString("\"&" + tag + "\":")
} else {
out.RawString(",\"&" + tag + "\":")
}
{
out.RawByte('[')
for i, v := range values {
if i > 0 {
out.RawByte(',')
}
out.String(string(v))
}
out.RawByte(']')
}
}
out.RawByte('}')
}

Expand Down
Loading