mirror of
https://github.com/idanoo/autobrr
synced 2025-07-23 08:49:13 +00:00
feat(filters): add match logic for tags and except tags (#810)
* feat(filters): add fields for tag and except tag matching logic * refactor(filters): rearrange and simplify logic for containsAllMatch --------- Co-authored-by: Gustavo Machado <me@gstv.dev>
This commit is contained in:
parent
d48e94ff33
commit
ef75b67b25
9 changed files with 364 additions and 76 deletions
|
@ -115,6 +115,8 @@ type Filter struct {
|
|||
ExceptTags string `json:"except_tags,omitempty"`
|
||||
TagsAny string `json:"tags_any,omitempty"`
|
||||
ExceptTagsAny string `json:"except_tags_any,omitempty"`
|
||||
TagsMatchLogic string `json:"tags_match_logic,omitempty"`
|
||||
ExceptTagsMatchLogic string `json:"except_tags_match_logic,omitempty"`
|
||||
MatchReleaseTags string `json:"match_release_tags,omitempty"`
|
||||
ExceptReleaseTags string `json:"except_release_tags,omitempty"`
|
||||
UseRegexReleaseTags bool `json:"use_regex_release_tags,omitempty"`
|
||||
|
@ -190,6 +192,8 @@ type FilterUpdate struct {
|
|||
ExceptTags *string `json:"except_tags,omitempty"`
|
||||
TagsAny *string `json:"tags_any,omitempty"`
|
||||
ExceptTagsAny *string `json:"except_tags_any,omitempty"`
|
||||
TagsMatchLogic *string `json:"tags_match_logic,omitempty"`
|
||||
ExceptTagsMatchLogic *string `json:"except_tags_match_logic,omitempty"`
|
||||
ExternalScriptEnabled *bool `json:"external_script_enabled,omitempty"`
|
||||
ExternalScriptCmd *string `json:"external_script_cmd,omitempty"`
|
||||
ExternalScriptArgs *string `json:"external_script_args,omitempty"`
|
||||
|
@ -379,12 +383,20 @@ func (f Filter) CheckFilter(r *Release) ([]string, bool) {
|
|||
r.addRejectionF("size not matching. got: %v want min: %v max: %v", r.Size, f.MinSize, f.MaxSize)
|
||||
}
|
||||
|
||||
if f.Tags != "" && !containsAny(r.Tags, f.Tags) {
|
||||
r.addRejectionF("tags not matching. got: %v want: %v", r.Tags, f.Tags)
|
||||
if f.Tags != "" {
|
||||
if f.TagsMatchLogic == "ANY" && !containsAny(r.Tags, f.Tags) {
|
||||
r.addRejectionF("tags not matching. got: %v want: %v", r.Tags, f.Tags)
|
||||
} else if f.TagsMatchLogic == "ALL" && !containsAll(r.Tags, f.Tags) {
|
||||
r.addRejectionF("tags not matching. got: %v want(all): %v", r.Tags, f.Tags)
|
||||
}
|
||||
}
|
||||
|
||||
if f.ExceptTags != "" && containsAny(r.Tags, f.ExceptTags) {
|
||||
r.addRejectionF("tags unwanted. got: %v want: %v", r.Tags, f.ExceptTags)
|
||||
if f.ExceptTags != "" {
|
||||
if f.ExceptTagsMatchLogic == "ANY" && containsAny(r.Tags, f.ExceptTags) {
|
||||
r.addRejectionF("tags unwanted. got: %v want: %v", r.Tags, f.ExceptTags)
|
||||
} else if f.ExceptTagsMatchLogic == "ALL" && containsAll(r.Tags, f.ExceptTags) {
|
||||
r.addRejectionF("tags unwanted. got: %v want(all): %v", r.Tags, f.ExceptTags)
|
||||
}
|
||||
}
|
||||
|
||||
if len(f.Artists) > 0 && !contains(r.Artists, f.Artists) {
|
||||
|
@ -620,6 +632,10 @@ func containsAny(tags []string, filter string) bool {
|
|||
return containsMatch(tags, strings.Split(filter, ","))
|
||||
}
|
||||
|
||||
func containsAll(tags []string, filter string) bool {
|
||||
return containsAllMatch(tags, strings.Split(filter, ","))
|
||||
}
|
||||
|
||||
func containsAnyOther(filter string, tags ...string) bool {
|
||||
return containsMatch(tags, strings.Split(filter, ","))
|
||||
}
|
||||
|
@ -686,6 +702,39 @@ func containsMatch(tags []string, filters []string) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func containsAllMatch(tags []string, filters []string) bool {
|
||||
for _, filter := range filters {
|
||||
if filter == "" {
|
||||
continue
|
||||
}
|
||||
filter = strings.ToLower(filter)
|
||||
filter = strings.Trim(filter, " ")
|
||||
found := false
|
||||
|
||||
for _, tag := range tags {
|
||||
if tag == "" {
|
||||
continue
|
||||
}
|
||||
tag = strings.ToLower(tag)
|
||||
|
||||
if tag == filter {
|
||||
found = true
|
||||
break
|
||||
} else if strings.ContainsAny(filter, "?|*") {
|
||||
if wildcard.Match(filter, tag) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func containsMatchBasic(tags []string, filters []string) bool {
|
||||
for _, tag := range tags {
|
||||
if tag == "" {
|
||||
|
|
|
@ -520,7 +520,7 @@ func TestFilter_CheckFilter(t *testing.T) {
|
|||
want: true,
|
||||
},
|
||||
{
|
||||
name: "match_tags",
|
||||
name: "match_tags_any",
|
||||
fields: &Release{
|
||||
TorrentName: "Good show S02 2160p ATVP WEB-DL DDP 5.1 Atmos DV HEVC-GROUP2",
|
||||
Category: "TV",
|
||||
|
@ -535,12 +535,34 @@ func TestFilter_CheckFilter(t *testing.T) {
|
|||
ExceptUploaders: "Anonymous",
|
||||
Shows: "Good show",
|
||||
Tags: "tv",
|
||||
TagsMatchLogic: "ANY",
|
||||
},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "match_tags_bad",
|
||||
name: "match_tags_all",
|
||||
fields: &Release{
|
||||
TorrentName: "Good show S02 2160p ATVP WEB-DL DDP 5.1 Atmos DV HEVC-GROUP2",
|
||||
Category: "TV",
|
||||
Uploader: "Uploader1",
|
||||
Tags: []string{"tv", "foreign"},
|
||||
},
|
||||
args: args{
|
||||
filter: Filter{
|
||||
Enabled: true,
|
||||
MatchCategories: "*tv*",
|
||||
MatchUploaders: "Uploader1,Uploader2",
|
||||
ExceptUploaders: "Anonymous",
|
||||
Shows: "Good show",
|
||||
Tags: "tv,foreign",
|
||||
TagsMatchLogic: "ALL",
|
||||
},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "match_tags_any_bad",
|
||||
fields: &Release{
|
||||
TorrentName: "Good show S02 2160p ATVP WEB-DL DDP 5.1 Atmos DV HEVC-GROUP2",
|
||||
Category: "TV",
|
||||
|
@ -555,13 +577,36 @@ func TestFilter_CheckFilter(t *testing.T) {
|
|||
ExceptUploaders: "Anonymous",
|
||||
Shows: "Good show",
|
||||
Tags: "tv",
|
||||
TagsMatchLogic: "ANY",
|
||||
},
|
||||
rejections: []string{"tags not matching. got: [foreign] want: tv"},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "match_except_tags",
|
||||
name: "match_tags_all_bad",
|
||||
fields: &Release{
|
||||
TorrentName: "Good show S02 2160p ATVP WEB-DL DDP 5.1 Atmos DV HEVC-GROUP2",
|
||||
Category: "TV",
|
||||
Uploader: "Uploader1",
|
||||
Tags: []string{"foreign"},
|
||||
},
|
||||
args: args{
|
||||
filter: Filter{
|
||||
Enabled: true,
|
||||
MatchCategories: "*tv*",
|
||||
MatchUploaders: "Uploader1,Uploader2",
|
||||
ExceptUploaders: "Anonymous",
|
||||
Shows: "Good show",
|
||||
Tags: "tv,foreign",
|
||||
TagsMatchLogic: "ALL",
|
||||
},
|
||||
rejections: []string{"tags not matching. got: [foreign] want(all): tv,foreign"},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "match_except_tags_any",
|
||||
fields: &Release{
|
||||
TorrentName: "Good show S02 2160p ATVP WEB-DL DDP 5.1 Atmos DV HEVC-GROUP2",
|
||||
Category: "TV",
|
||||
|
@ -576,12 +621,13 @@ func TestFilter_CheckFilter(t *testing.T) {
|
|||
ExceptUploaders: "Anonymous",
|
||||
Shows: "Good show",
|
||||
ExceptTags: "tv",
|
||||
TagsMatchLogic: "ANY",
|
||||
},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "match_except_tags_2",
|
||||
name: "match_except_tags_all",
|
||||
fields: &Release{
|
||||
TorrentName: "Good show S02 2160p ATVP WEB-DL DDP 5.1 Atmos DV HEVC-GROUP2",
|
||||
Category: "TV",
|
||||
|
@ -595,12 +641,56 @@ func TestFilter_CheckFilter(t *testing.T) {
|
|||
MatchUploaders: "Uploader1,Uploader2",
|
||||
ExceptUploaders: "Anonymous",
|
||||
Shows: "Good show",
|
||||
ExceptTags: "foreign",
|
||||
ExceptTags: "tv,foreign",
|
||||
TagsMatchLogic: "ALL",
|
||||
},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "match_except_tags_any_2",
|
||||
fields: &Release{
|
||||
TorrentName: "Good show S02 2160p ATVP WEB-DL DDP 5.1 Atmos DV HEVC-GROUP2",
|
||||
Category: "TV",
|
||||
Uploader: "Uploader1",
|
||||
Tags: []string{"foreign"},
|
||||
},
|
||||
args: args{
|
||||
filter: Filter{
|
||||
Enabled: true,
|
||||
MatchCategories: "*tv*",
|
||||
MatchUploaders: "Uploader1,Uploader2",
|
||||
ExceptUploaders: "Anonymous",
|
||||
Shows: "Good show",
|
||||
ExceptTags: "foreign",
|
||||
ExceptTagsMatchLogic: "ANY",
|
||||
},
|
||||
rejections: []string{"tags unwanted. got: [foreign] want: foreign"},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "match_except_tags_all_2",
|
||||
fields: &Release{
|
||||
TorrentName: "Good show S02 2160p ATVP WEB-DL DDP 5.1 Atmos DV HEVC-GROUP2",
|
||||
Category: "TV",
|
||||
Uploader: "Uploader1",
|
||||
Tags: []string{"tv", "foreign"},
|
||||
},
|
||||
args: args{
|
||||
filter: Filter{
|
||||
Enabled: true,
|
||||
MatchCategories: "*tv*",
|
||||
MatchUploaders: "Uploader1,Uploader2",
|
||||
ExceptUploaders: "Anonymous",
|
||||
Shows: "Good show",
|
||||
ExceptTags: "foreign,tv",
|
||||
ExceptTagsMatchLogic: "ALL",
|
||||
},
|
||||
rejections: []string{"tags unwanted. got: [tv foreign] want(all): foreign,tv"},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "match_group_1",
|
||||
fields: &Release{
|
||||
|
@ -1717,64 +1807,64 @@ func TestFilter_CheckFilter1(t *testing.T) {
|
|||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
f := Filter{
|
||||
ID: tt.fields.ID,
|
||||
Name: tt.fields.Name,
|
||||
Enabled: tt.fields.Enabled,
|
||||
CreatedAt: tt.fields.CreatedAt,
|
||||
UpdatedAt: tt.fields.UpdatedAt,
|
||||
MinSize: tt.fields.MinSize,
|
||||
MaxSize: tt.fields.MaxSize,
|
||||
Delay: tt.fields.Delay,
|
||||
Priority: tt.fields.Priority,
|
||||
MaxDownloads: tt.fields.MaxDownloads,
|
||||
MaxDownloadsUnit: tt.fields.MaxDownloadsUnit,
|
||||
MatchReleases: tt.fields.MatchReleases,
|
||||
ExceptReleases: tt.fields.ExceptReleases,
|
||||
UseRegex: tt.fields.UseRegex,
|
||||
MatchReleaseGroups: tt.fields.MatchReleaseGroups,
|
||||
ExceptReleaseGroups: tt.fields.ExceptReleaseGroups,
|
||||
MatchReleaseTags: tt.fields.MatchReleaseTags,
|
||||
ExceptReleaseTags: tt.fields.ExceptReleaseTags,
|
||||
UseRegexReleaseTags: tt.fields.UseRegexReleaseTags,
|
||||
Scene: tt.fields.Scene,
|
||||
Origins: tt.fields.Origins,
|
||||
ExceptOrigins: tt.fields.ExceptOrigins,
|
||||
Freeleech: tt.fields.Freeleech,
|
||||
FreeleechPercent: tt.fields.FreeleechPercent,
|
||||
Shows: tt.fields.Shows,
|
||||
Seasons: tt.fields.Seasons,
|
||||
Episodes: tt.fields.Episodes,
|
||||
Resolutions: tt.fields.Resolutions,
|
||||
Codecs: tt.fields.Codecs,
|
||||
Sources: tt.fields.Sources,
|
||||
Containers: tt.fields.Containers,
|
||||
MatchHDR: tt.fields.MatchHDR,
|
||||
ExceptHDR: tt.fields.ExceptHDR,
|
||||
Years: tt.fields.Years,
|
||||
Artists: tt.fields.Artists,
|
||||
Albums: tt.fields.Albums,
|
||||
MatchReleaseTypes: tt.fields.MatchReleaseTypes,
|
||||
ExceptReleaseTypes: tt.fields.ExceptReleaseTypes,
|
||||
Formats: tt.fields.Formats,
|
||||
Quality: tt.fields.Quality,
|
||||
Media: tt.fields.Media,
|
||||
PerfectFlac: tt.fields.PerfectFlac,
|
||||
Cue: tt.fields.Cue,
|
||||
Log: tt.fields.Log,
|
||||
LogScore: tt.fields.LogScore,
|
||||
MatchOther: tt.fields.MatchOther,
|
||||
ExceptOther: tt.fields.ExceptOther,
|
||||
MatchCategories: tt.fields.MatchCategories,
|
||||
ExceptCategories: tt.fields.ExceptCategories,
|
||||
MatchUploaders: tt.fields.MatchUploaders,
|
||||
ExceptUploaders: tt.fields.ExceptUploaders,
|
||||
Tags: tt.fields.Tags,
|
||||
ExceptTags: tt.fields.ExceptTags,
|
||||
TagsAny: tt.fields.TagsAny,
|
||||
ExceptTagsAny: tt.fields.ExceptTagsAny,
|
||||
Actions: tt.fields.Actions,
|
||||
Indexers: tt.fields.Indexers,
|
||||
Downloads: tt.fields.Downloads,
|
||||
ID: tt.fields.ID,
|
||||
Name: tt.fields.Name,
|
||||
Enabled: tt.fields.Enabled,
|
||||
CreatedAt: tt.fields.CreatedAt,
|
||||
UpdatedAt: tt.fields.UpdatedAt,
|
||||
MinSize: tt.fields.MinSize,
|
||||
MaxSize: tt.fields.MaxSize,
|
||||
Delay: tt.fields.Delay,
|
||||
Priority: tt.fields.Priority,
|
||||
MaxDownloads: tt.fields.MaxDownloads,
|
||||
MaxDownloadsUnit: tt.fields.MaxDownloadsUnit,
|
||||
MatchReleases: tt.fields.MatchReleases,
|
||||
ExceptReleases: tt.fields.ExceptReleases,
|
||||
UseRegex: tt.fields.UseRegex,
|
||||
MatchReleaseGroups: tt.fields.MatchReleaseGroups,
|
||||
ExceptReleaseGroups: tt.fields.ExceptReleaseGroups,
|
||||
MatchReleaseTags: tt.fields.MatchReleaseTags,
|
||||
ExceptReleaseTags: tt.fields.ExceptReleaseTags,
|
||||
UseRegexReleaseTags: tt.fields.UseRegexReleaseTags,
|
||||
Scene: tt.fields.Scene,
|
||||
Origins: tt.fields.Origins,
|
||||
ExceptOrigins: tt.fields.ExceptOrigins,
|
||||
Freeleech: tt.fields.Freeleech,
|
||||
FreeleechPercent: tt.fields.FreeleechPercent,
|
||||
Shows: tt.fields.Shows,
|
||||
Seasons: tt.fields.Seasons,
|
||||
Episodes: tt.fields.Episodes,
|
||||
Resolutions: tt.fields.Resolutions,
|
||||
Codecs: tt.fields.Codecs,
|
||||
Sources: tt.fields.Sources,
|
||||
Containers: tt.fields.Containers,
|
||||
MatchHDR: tt.fields.MatchHDR,
|
||||
ExceptHDR: tt.fields.ExceptHDR,
|
||||
Years: tt.fields.Years,
|
||||
Artists: tt.fields.Artists,
|
||||
Albums: tt.fields.Albums,
|
||||
MatchReleaseTypes: tt.fields.MatchReleaseTypes,
|
||||
ExceptReleaseTypes: tt.fields.ExceptReleaseTypes,
|
||||
Formats: tt.fields.Formats,
|
||||
Quality: tt.fields.Quality,
|
||||
Media: tt.fields.Media,
|
||||
PerfectFlac: tt.fields.PerfectFlac,
|
||||
Cue: tt.fields.Cue,
|
||||
Log: tt.fields.Log,
|
||||
LogScore: tt.fields.LogScore,
|
||||
MatchOther: tt.fields.MatchOther,
|
||||
ExceptOther: tt.fields.ExceptOther,
|
||||
MatchCategories: tt.fields.MatchCategories,
|
||||
ExceptCategories: tt.fields.ExceptCategories,
|
||||
MatchUploaders: tt.fields.MatchUploaders,
|
||||
ExceptUploaders: tt.fields.ExceptUploaders,
|
||||
Tags: tt.fields.Tags,
|
||||
ExceptTags: tt.fields.ExceptTags,
|
||||
TagsMatchLogic: tt.fields.TagsMatchLogic,
|
||||
ExceptTagsMatchLogic: tt.fields.ExceptTagsMatchLogic,
|
||||
Actions: tt.fields.Actions,
|
||||
Indexers: tt.fields.Indexers,
|
||||
Downloads: tt.fields.Downloads,
|
||||
}
|
||||
tt.args.r.ParseString(tt.args.r.TorrentName)
|
||||
rejections, match := f.CheckFilter(tt.args.r)
|
||||
|
@ -1784,6 +1874,54 @@ func TestFilter_CheckFilter1(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func Test_containsMatch(t *testing.T) {
|
||||
type args struct {
|
||||
tags []string
|
||||
filters []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
{name: "test_1", args: args{tags: []string{"HDR", "DV"}, filters: []string{"DV"}}, want: true},
|
||||
{name: "test_2", args: args{tags: []string{"HDR", "DV"}, filters: []string{"HD*", "D*"}}, want: true},
|
||||
{name: "test_3", args: args{tags: []string{"HDR"}, filters: []string{"DV"}}, want: false},
|
||||
{name: "test_4", args: args{tags: []string{"HDR"}, filters: []string{"TEST*"}}, want: false},
|
||||
{name: "test_5", args: args{tags: []string{""}, filters: []string{"test,"}}, want: false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equalf(t, tt.want, containsMatch(tt.args.tags, tt.args.filters), "containsMatch(%v, %v)", tt.args.tags, tt.args.filters)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_containsAllMatch(t *testing.T) {
|
||||
type args struct {
|
||||
tags []string
|
||||
filters []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
{name: "test_1", args: args{tags: []string{"HDR", "DV"}, filters: []string{"DV"}}, want: true},
|
||||
{name: "test_2", args: args{tags: []string{"HDR", "DV"}, filters: []string{"DV", "DoVI"}}, want: false},
|
||||
{name: "test_3", args: args{tags: []string{"HDR", "DV", "DoVI"}, filters: []string{"DV", "DoVI"}}, want: true},
|
||||
{name: "test_4", args: args{tags: []string{"HDR", "DV"}, filters: []string{"HD*", "D*"}}, want: true},
|
||||
{name: "test_5", args: args{tags: []string{"HDR", "DV"}, filters: []string{"HD*", "TEST*"}}, want: false},
|
||||
{name: "test_6", args: args{tags: []string{"HDR"}, filters: []string{"DV"}}, want: false},
|
||||
{name: "test_7", args: args{tags: []string{""}, filters: []string{"test,"}}, want: false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equalf(t, tt.want, containsAllMatch(tt.args.tags, tt.args.filters), "containsAllMatch(%v, %v)", tt.args.tags, tt.args.filters)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_contains(t *testing.T) {
|
||||
type args struct {
|
||||
tag string
|
||||
|
@ -1854,6 +1992,31 @@ func Test_containsAny(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func Test_containsAll(t *testing.T) {
|
||||
type args struct {
|
||||
tags []string
|
||||
filter string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
{name: "test_1", args: args{tags: []string{"HDR", "DV"}, filter: "DV"}, want: true},
|
||||
{name: "test_2", args: args{tags: []string{"HDR", "DV"}, filter: "HDR,DV"}, want: true},
|
||||
{name: "test_2", args: args{tags: []string{"HDR", "DV"}, filter: "HD*,D*"}, want: true},
|
||||
{name: "test_3", args: args{tags: []string{"HDR", "DoVI"}, filter: "HDR,DV"}, want: false},
|
||||
{name: "test_4", args: args{tags: []string{"HDR", "DV", "HDR10+"}, filter: "HDR,DV"}, want: true},
|
||||
{name: "test_5", args: args{tags: []string{"HDR"}, filter: "DV"}, want: false},
|
||||
{name: "test_6", args: args{tags: []string{""}, filter: "test,"}, want: false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equalf(t, tt.want, containsAll(tt.args.tags, tt.args.filter), "containsAll(%v, %v)", tt.args.tags, tt.args.filter)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_sliceContainsSlice(t *testing.T) {
|
||||
type args struct {
|
||||
tags []string
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue