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
|
@ -221,6 +221,8 @@ func (r *FilterRepo) FindByID(ctx context.Context, filterID int) (*domain.Filter
|
|||
"except_language",
|
||||
"tags",
|
||||
"except_tags",
|
||||
"tags_match_logic",
|
||||
"except_tags_match_logic",
|
||||
"origins",
|
||||
"except_origins",
|
||||
"external_script_enabled",
|
||||
|
@ -248,11 +250,11 @@ func (r *FilterRepo) FindByID(ctx context.Context, filterID int) (*domain.Filter
|
|||
}
|
||||
|
||||
var f domain.Filter
|
||||
var minSize, maxSize, maxDownloadsUnit, matchReleases, exceptReleases, matchReleaseGroups, exceptReleaseGroups, matchReleaseTags, exceptReleaseTags, freeleechPercent, shows, seasons, episodes, years, artists, albums, matchCategories, exceptCategories, matchUploaders, exceptUploaders, tags, exceptTags, extScriptCmd, extScriptArgs, extWebhookHost, extWebhookData sql.NullString
|
||||
var minSize, maxSize, maxDownloadsUnit, matchReleases, exceptReleases, matchReleaseGroups, exceptReleaseGroups, matchReleaseTags, exceptReleaseTags, freeleechPercent, shows, seasons, episodes, years, artists, albums, matchCategories, exceptCategories, matchUploaders, exceptUploaders, tags, exceptTags, tagsMatchLogic, exceptTagsMatchLogic, extScriptCmd, extScriptArgs, extWebhookHost, extWebhookData sql.NullString
|
||||
var useRegex, scene, freeleech, hasLog, hasCue, perfectFlac, extScriptEnabled, extWebhookEnabled sql.NullBool
|
||||
var delay, maxDownloads, logScore, extWebhookStatus, extScriptStatus sql.NullInt32
|
||||
|
||||
if err := row.Scan(&f.ID, &f.Enabled, &f.Name, &minSize, &maxSize, &delay, &f.Priority, &maxDownloads, &maxDownloadsUnit, &matchReleases, &exceptReleases, &useRegex, &matchReleaseGroups, &exceptReleaseGroups, &matchReleaseTags, &exceptReleaseTags, &f.UseRegexReleaseTags, &scene, &freeleech, &freeleechPercent, &f.SmartEpisode, &shows, &seasons, &episodes, pq.Array(&f.Resolutions), pq.Array(&f.Codecs), pq.Array(&f.Sources), pq.Array(&f.Containers), pq.Array(&f.MatchHDR), pq.Array(&f.ExceptHDR), pq.Array(&f.MatchOther), pq.Array(&f.ExceptOther), &years, &artists, &albums, pq.Array(&f.MatchReleaseTypes), pq.Array(&f.Formats), pq.Array(&f.Quality), pq.Array(&f.Media), &logScore, &hasLog, &hasCue, &perfectFlac, &matchCategories, &exceptCategories, &matchUploaders, &exceptUploaders, pq.Array(&f.MatchLanguage), pq.Array(&f.ExceptLanguage), &tags, &exceptTags, pq.Array(&f.Origins), pq.Array(&f.ExceptOrigins), &extScriptEnabled, &extScriptCmd, &extScriptArgs, &extScriptStatus, &extWebhookEnabled, &extWebhookHost, &extWebhookData, &extWebhookStatus, &f.CreatedAt, &f.UpdatedAt); err != nil {
|
||||
if err := row.Scan(&f.ID, &f.Enabled, &f.Name, &minSize, &maxSize, &delay, &f.Priority, &maxDownloads, &maxDownloadsUnit, &matchReleases, &exceptReleases, &useRegex, &matchReleaseGroups, &exceptReleaseGroups, &matchReleaseTags, &exceptReleaseTags, &f.UseRegexReleaseTags, &scene, &freeleech, &freeleechPercent, &f.SmartEpisode, &shows, &seasons, &episodes, pq.Array(&f.Resolutions), pq.Array(&f.Codecs), pq.Array(&f.Sources), pq.Array(&f.Containers), pq.Array(&f.MatchHDR), pq.Array(&f.ExceptHDR), pq.Array(&f.MatchOther), pq.Array(&f.ExceptOther), &years, &artists, &albums, pq.Array(&f.MatchReleaseTypes), pq.Array(&f.Formats), pq.Array(&f.Quality), pq.Array(&f.Media), &logScore, &hasLog, &hasCue, &perfectFlac, &matchCategories, &exceptCategories, &matchUploaders, &exceptUploaders, pq.Array(&f.MatchLanguage), pq.Array(&f.ExceptLanguage), &tags, &exceptTags, &tagsMatchLogic, &exceptTagsMatchLogic, pq.Array(&f.Origins), pq.Array(&f.ExceptOrigins), &extScriptEnabled, &extScriptCmd, &extScriptArgs, &extScriptStatus, &extWebhookEnabled, &extWebhookHost, &extWebhookData, &extWebhookStatus, &f.CreatedAt, &f.UpdatedAt); err != nil {
|
||||
return nil, errors.Wrap(err, "error scanning row")
|
||||
}
|
||||
|
||||
|
@ -284,6 +286,8 @@ func (r *FilterRepo) FindByID(ctx context.Context, filterID int) (*domain.Filter
|
|||
f.ExceptUploaders = exceptUploaders.String
|
||||
f.Tags = tags.String
|
||||
f.ExceptTags = exceptTags.String
|
||||
f.TagsMatchLogic = tagsMatchLogic.String
|
||||
f.ExceptTagsMatchLogic = exceptTagsMatchLogic.String
|
||||
f.UseRegex = useRegex.Bool
|
||||
f.Scene = scene.Bool
|
||||
f.Freeleech = freeleech.Bool
|
||||
|
@ -384,6 +388,8 @@ func (r *FilterRepo) findByIndexerIdentifier(ctx context.Context, tx *Tx, indexe
|
|||
"f.except_language",
|
||||
"f.tags",
|
||||
"f.except_tags",
|
||||
"f.tags_match_logic",
|
||||
"f.except_tags_match_logic",
|
||||
"f.origins",
|
||||
"f.except_origins",
|
||||
"f.external_script_enabled",
|
||||
|
@ -421,11 +427,11 @@ func (r *FilterRepo) findByIndexerIdentifier(ctx context.Context, tx *Tx, indexe
|
|||
for rows.Next() {
|
||||
var f domain.Filter
|
||||
|
||||
var minSize, maxSize, maxDownloadsUnit, matchReleases, exceptReleases, matchReleaseGroups, exceptReleaseGroups, matchReleaseTags, exceptReleaseTags, freeleechPercent, shows, seasons, episodes, years, artists, albums, matchCategories, exceptCategories, matchUploaders, exceptUploaders, tags, exceptTags, extScriptCmd, extScriptArgs, extWebhookHost, extWebhookData sql.NullString
|
||||
var minSize, maxSize, maxDownloadsUnit, matchReleases, exceptReleases, matchReleaseGroups, exceptReleaseGroups, matchReleaseTags, exceptReleaseTags, freeleechPercent, shows, seasons, episodes, years, artists, albums, matchCategories, exceptCategories, matchUploaders, exceptUploaders, tags, exceptTags, tagsMatchLogic, exceptTagsMatchLogic, extScriptCmd, extScriptArgs, extWebhookHost, extWebhookData sql.NullString
|
||||
var useRegex, scene, freeleech, hasLog, hasCue, perfectFlac, extScriptEnabled, extWebhookEnabled sql.NullBool
|
||||
var delay, maxDownloads, logScore, extWebhookStatus, extScriptStatus sql.NullInt32
|
||||
|
||||
if err := rows.Scan(&f.ID, &f.Enabled, &f.Name, &minSize, &maxSize, &delay, &f.Priority, &maxDownloads, &maxDownloadsUnit, &matchReleases, &exceptReleases, &useRegex, &matchReleaseGroups, &exceptReleaseGroups, &matchReleaseTags, &exceptReleaseTags, &f.UseRegexReleaseTags, &scene, &freeleech, &freeleechPercent, &f.SmartEpisode, &shows, &seasons, &episodes, pq.Array(&f.Resolutions), pq.Array(&f.Codecs), pq.Array(&f.Sources), pq.Array(&f.Containers), pq.Array(&f.MatchHDR), pq.Array(&f.ExceptHDR), pq.Array(&f.MatchOther), pq.Array(&f.ExceptOther), &years, &artists, &albums, pq.Array(&f.MatchReleaseTypes), pq.Array(&f.Formats), pq.Array(&f.Quality), pq.Array(&f.Media), &logScore, &hasLog, &hasCue, &perfectFlac, &matchCategories, &exceptCategories, &matchUploaders, &exceptUploaders, pq.Array(&f.MatchLanguage), pq.Array(&f.ExceptLanguage), &tags, &exceptTags, pq.Array(&f.Origins), pq.Array(&f.ExceptOrigins), &extScriptEnabled, &extScriptCmd, &extScriptArgs, &extScriptStatus, &extWebhookEnabled, &extWebhookHost, &extWebhookData, &extWebhookStatus, &f.CreatedAt, &f.UpdatedAt); err != nil {
|
||||
if err := rows.Scan(&f.ID, &f.Enabled, &f.Name, &minSize, &maxSize, &delay, &f.Priority, &maxDownloads, &maxDownloadsUnit, &matchReleases, &exceptReleases, &useRegex, &matchReleaseGroups, &exceptReleaseGroups, &matchReleaseTags, &exceptReleaseTags, &f.UseRegexReleaseTags, &scene, &freeleech, &freeleechPercent, &f.SmartEpisode, &shows, &seasons, &episodes, pq.Array(&f.Resolutions), pq.Array(&f.Codecs), pq.Array(&f.Sources), pq.Array(&f.Containers), pq.Array(&f.MatchHDR), pq.Array(&f.ExceptHDR), pq.Array(&f.MatchOther), pq.Array(&f.ExceptOther), &years, &artists, &albums, pq.Array(&f.MatchReleaseTypes), pq.Array(&f.Formats), pq.Array(&f.Quality), pq.Array(&f.Media), &logScore, &hasLog, &hasCue, &perfectFlac, &matchCategories, &exceptCategories, &matchUploaders, &exceptUploaders, pq.Array(&f.MatchLanguage), pq.Array(&f.ExceptLanguage), &tags, &exceptTags, &tagsMatchLogic, &exceptTagsMatchLogic, pq.Array(&f.Origins), pq.Array(&f.ExceptOrigins), &extScriptEnabled, &extScriptCmd, &extScriptArgs, &extScriptStatus, &extWebhookEnabled, &extWebhookHost, &extWebhookData, &extWebhookStatus, &f.CreatedAt, &f.UpdatedAt); err != nil {
|
||||
return nil, errors.Wrap(err, "error scanning row")
|
||||
}
|
||||
|
||||
|
@ -457,6 +463,8 @@ func (r *FilterRepo) findByIndexerIdentifier(ctx context.Context, tx *Tx, indexe
|
|||
f.ExceptUploaders = exceptUploaders.String
|
||||
f.Tags = tags.String
|
||||
f.ExceptTags = exceptTags.String
|
||||
f.TagsMatchLogic = tagsMatchLogic.String
|
||||
f.ExceptTagsMatchLogic = exceptTagsMatchLogic.String
|
||||
f.UseRegex = useRegex.Bool
|
||||
f.Scene = scene.Bool
|
||||
f.Freeleech = freeleech.Bool
|
||||
|
@ -521,6 +529,8 @@ func (r *FilterRepo) Store(ctx context.Context, filter domain.Filter) (*domain.F
|
|||
"except_language",
|
||||
"tags",
|
||||
"except_tags",
|
||||
"tags_match_logic",
|
||||
"except_tags_match_logic",
|
||||
"artists",
|
||||
"albums",
|
||||
"release_types_match",
|
||||
|
@ -583,6 +593,8 @@ func (r *FilterRepo) Store(ctx context.Context, filter domain.Filter) (*domain.F
|
|||
pq.Array(filter.ExceptLanguage),
|
||||
filter.Tags,
|
||||
filter.ExceptTags,
|
||||
filter.TagsMatchLogic,
|
||||
filter.ExceptTagsMatchLogic,
|
||||
filter.Artists,
|
||||
filter.Albums,
|
||||
pq.Array(filter.MatchReleaseTypes),
|
||||
|
@ -664,6 +676,8 @@ func (r *FilterRepo) Update(ctx context.Context, filter domain.Filter) (*domain.
|
|||
Set("except_language", pq.Array(filter.ExceptLanguage)).
|
||||
Set("tags", filter.Tags).
|
||||
Set("except_tags", filter.ExceptTags).
|
||||
Set("tags_match_logic", filter.TagsMatchLogic).
|
||||
Set("except_tags_match_logic", filter.ExceptTagsMatchLogic).
|
||||
Set("artists", filter.Artists).
|
||||
Set("albums", filter.Albums).
|
||||
Set("release_types_match", pq.Array(filter.MatchReleaseTypes)).
|
||||
|
@ -825,6 +839,12 @@ func (r *FilterRepo) UpdatePartial(ctx context.Context, filter domain.FilterUpda
|
|||
if filter.ExceptTags != nil {
|
||||
q = q.Set("except_tags", filter.ExceptTags)
|
||||
}
|
||||
if filter.TagsMatchLogic != nil {
|
||||
q = q.Set("tags_match_logic", filter.TagsMatchLogic)
|
||||
}
|
||||
if filter.ExceptTagsMatchLogic != nil {
|
||||
q = q.Set("except_tags_match_logic", filter.ExceptTagsMatchLogic)
|
||||
}
|
||||
if filter.Artists != nil {
|
||||
q = q.Set("artists", filter.Artists)
|
||||
}
|
||||
|
|
|
@ -115,6 +115,8 @@ CREATE TABLE filter
|
|||
except_language TEXT [] DEFAULT '{}',
|
||||
tags TEXT,
|
||||
except_tags TEXT,
|
||||
tags_match_logic TEXT,
|
||||
except_tags_match_logic TEXT,
|
||||
origins TEXT [] DEFAULT '{}',
|
||||
except_origins TEXT [] DEFAULT '{}',
|
||||
external_script_enabled BOOLEAN DEFAULT FALSE,
|
||||
|
@ -655,4 +657,18 @@ ADD COLUMN info_url TEXT;
|
|||
ALTER TABLE "release"
|
||||
ADD COLUMN download_url TEXT;
|
||||
`,
|
||||
`ALTER TABLE filter
|
||||
ADD COLUMN tags_match_logic TEXT;
|
||||
|
||||
ALTER TABLE filter
|
||||
ADD COLUMN except_tags_match_logic TEXT;
|
||||
|
||||
UPDATE filter
|
||||
SET tags_match_logic = 'ANY'
|
||||
WHERE tags IS NOT NULL;
|
||||
|
||||
UPDATE filter
|
||||
SET except_tags_match_logic = 'ANY'
|
||||
WHERE except_tags IS NOT NULL;
|
||||
`,
|
||||
}
|
||||
|
|
|
@ -115,6 +115,8 @@ CREATE TABLE filter
|
|||
except_language TEXT [] DEFAULT '{}',
|
||||
tags TEXT,
|
||||
except_tags TEXT,
|
||||
tags_match_logic TEXT,
|
||||
except_tags_match_logic TEXT,
|
||||
origins TEXT [] DEFAULT '{}',
|
||||
except_origins TEXT [] DEFAULT '{}',
|
||||
external_script_enabled BOOLEAN DEFAULT FALSE,
|
||||
|
@ -1048,4 +1050,18 @@ ADD COLUMN info_url TEXT;
|
|||
ALTER TABLE "release"
|
||||
ADD COLUMN download_url TEXT;
|
||||
`,
|
||||
`ALTER TABLE filter
|
||||
ADD COLUMN tags_match_logic TEXT;
|
||||
|
||||
ALTER TABLE filter
|
||||
ADD COLUMN except_tags_match_logic TEXT;
|
||||
|
||||
UPDATE filter
|
||||
SET tags_match_logic = 'ANY'
|
||||
WHERE tags IS NOT NULL;
|
||||
|
||||
UPDATE filter
|
||||
SET except_tags_match_logic = 'ANY'
|
||||
WHERE except_tags IS NOT NULL;
|
||||
`,
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -251,6 +251,7 @@ export interface SelectFieldProps {
|
|||
label: string;
|
||||
optionDefaultText: string;
|
||||
options: SelectFieldOption[];
|
||||
columns?: COL_WIDTHS;
|
||||
tooltip?: JSX.Element;
|
||||
}
|
||||
|
||||
|
@ -259,10 +260,15 @@ export const Select = ({
|
|||
label,
|
||||
tooltip,
|
||||
optionDefaultText,
|
||||
options
|
||||
options,
|
||||
columns
|
||||
}: SelectFieldProps) => {
|
||||
return (
|
||||
<div className="col-span-6">
|
||||
<div
|
||||
className={classNames(
|
||||
columns ? `col-span-${columns}` : "col-span-6"
|
||||
)}
|
||||
>
|
||||
<Field name={name} type="select">
|
||||
{({
|
||||
field,
|
||||
|
|
|
@ -500,3 +500,14 @@ export const FeedDownloadTypeOptions: OptionBasicTyped<FeedDownloadType>[] = [
|
|||
value: "TORRENT"
|
||||
}
|
||||
];
|
||||
|
||||
export const tagsMatchLogicOptions: OptionBasic[] = [
|
||||
{
|
||||
label: "any",
|
||||
value: "ANY"
|
||||
},
|
||||
{
|
||||
label: "all",
|
||||
value: "ALL"
|
||||
}
|
||||
];
|
|
@ -18,7 +18,8 @@ import {
|
|||
RELEASE_TYPE_MUSIC_OPTIONS,
|
||||
RESOLUTION_OPTIONS,
|
||||
SOURCES_MUSIC_OPTIONS,
|
||||
SOURCES_OPTIONS
|
||||
SOURCES_OPTIONS,
|
||||
tagsMatchLogicOptions
|
||||
} from "../../domain/constants";
|
||||
import { queryClient } from "../../App";
|
||||
import { APIClient } from "../../api/APIClient";
|
||||
|
@ -265,6 +266,8 @@ export default function FilterDetails() {
|
|||
except_categories: filter.except_categories,
|
||||
tags: filter.tags,
|
||||
except_tags: filter.except_tags,
|
||||
tags_match_logic: filter.tags_match_logic,
|
||||
except_tags_match_logic: filter.except_tags_match_logic,
|
||||
match_uploaders: filter.match_uploaders,
|
||||
except_uploaders: filter.except_uploaders,
|
||||
match_language: filter.match_language || [],
|
||||
|
@ -509,8 +512,10 @@ export function Advanced({ values }: AdvancedProps) {
|
|||
<TextField name="match_categories" label="Match categories" columns={6} placeholder="eg. *category*,category1" tooltip={<div><p>Comma separated list of categories to match.</p><a href='https://autobrr.com/filters/categories' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters/categories</a></div>} />
|
||||
<TextField name="except_categories" label="Except categories" columns={6} placeholder="eg. *category*" tooltip={<div><p>Comma separated list of categories to ignore (takes priority over Match releases).</p><a href='https://autobrr.com/filters/categories' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters/categories</a></div>} />
|
||||
|
||||
<TextField name="tags" label="Match tags" columns={6} placeholder="eg. tag1,tag2" tooltip={<div><p>Comma separated list of tags to match.</p><a href='https://autobrr.com/filters#advanced' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters#advanced</a></div>} />
|
||||
<TextField name="except_tags" label="Except tags" columns={6} placeholder="eg. tag1,tag2" tooltip={<div><p>Comma separated list of tags to ignore (takes priority over Match releases).</p><a href='https://autobrr.com/filters#advanced' className='text-blue-400 visited:text-blue-400' target='_blank'>hhttps://autobrr.com/filters#advanced</a></div>} />
|
||||
<TextField name="tags" label="Match tags" columns={4} placeholder="eg. tag1,tag2" tooltip={<div><p>Comma separated list of tags to match.</p><a href='https://autobrr.com/filters#advanced' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters#advanced</a></div>} />
|
||||
<Select name="tags_match_logic" label="Tags logic" columns={2} options={tagsMatchLogicOptions} optionDefaultText="any" tooltip={<div><p>Logic used to match filter tags.</p><a href='https://autobrr.com/filters#advanced' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters#advanced</a></div>} />
|
||||
<TextField name="except_tags" label="Except tags" columns={4} placeholder="eg. tag1,tag2" tooltip={<div><p>Comma separated list of tags to ignore (takes priority over Match releases).</p><a href='https://autobrr.com/filters#advanced' className='text-blue-400 visited:text-blue-400' target='_blank'>hhttps://autobrr.com/filters#advanced</a></div>} />
|
||||
<Select name="except_tags_match_logic" label="Except tags logic" columns={2} options={tagsMatchLogicOptions} optionDefaultText="any" tooltip={<div><p>Logic used to match except tags.</p><a href='https://autobrr.com/filters#advanced' className='text-blue-400 visited:text-blue-400' target='_blank'>https://autobrr.com/filters#advanced</a></div>} />
|
||||
</CollapsableSection>
|
||||
|
||||
<CollapsableSection defaultOpen={true} title="Uploaders" subtitle="Match or ignore uploaders.">
|
||||
|
|
2
web/src/types/Filter.d.ts
vendored
2
web/src/types/Filter.d.ts
vendored
|
@ -57,6 +57,8 @@ interface Filter {
|
|||
except_tags: string;
|
||||
tags_any: string;
|
||||
except_tags_any: string;
|
||||
tags_match_logic: string;
|
||||
except_tags_match_logic: string;
|
||||
actions_count: number;
|
||||
actions: Action[];
|
||||
indexers: Indexer[];
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue