feat(filters): support daily shows (#1462)

* feat(database): Add month, day columns to release table

* feat(database): Add month, day columns to postgres release table

* feat(filters): support daily show format

* feat(filters): check smart episode daily

* fix(tests): rss

* feat(filters): add daily shows elements to form

* enhancement(web): minimize html in MoviesAndTV tab

* feat(filters): smart episode check proper and repack

* feat(filters): smart episode do not allow multiple latest

* feat(filters): smart episode check group with repack

* feat(filters): smart episode allow multiple current releases

---------

Co-authored-by: s0up4200 <soup@r4tio.dev>
Co-authored-by: ze0s <43699394+zze0s@users.noreply.github.com>
Co-authored-by: martylukyy <35452459+martylukyy@users.noreply.github.com>
This commit is contained in:
kenstir 2024-05-15 10:38:10 -04:00 committed by GitHub
parent 2a3dcfbf05
commit 4fceccd611
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 270 additions and 56 deletions

View file

@ -215,6 +215,8 @@ func (r *FilterRepo) FindByID(ctx context.Context, filterID int) (*domain.Filter
"f.match_other",
"f.except_other",
"f.years",
"f.months",
"f.days",
"f.artists",
"f.albums",
"f.release_types_match",
@ -265,7 +267,7 @@ func (r *FilterRepo) FindByID(ctx context.Context, filterID int) (*domain.Filter
var f domain.Filter
// filter
var minSize, maxSize, maxDownloadsUnit, matchReleases, exceptReleases, matchReleaseGroups, exceptReleaseGroups, matchReleaseTags, exceptReleaseTags, matchDescription, exceptDescription, freeleechPercent, shows, seasons, episodes, years, artists, albums, matchCategories, exceptCategories, matchUploaders, exceptUploaders, tags, exceptTags, tagsMatchLogic, exceptTagsMatchLogic sql.NullString
var minSize, maxSize, maxDownloadsUnit, matchReleases, exceptReleases, matchReleaseGroups, exceptReleaseGroups, matchReleaseTags, exceptReleaseTags, matchDescription, exceptDescription, freeleechPercent, shows, seasons, episodes, years, months, days, artists, albums, matchCategories, exceptCategories, matchUploaders, exceptUploaders, tags, exceptTags, tagsMatchLogic, exceptTagsMatchLogic sql.NullString
var useRegex, scene, freeleech, hasLog, hasCue, perfectFlac sql.NullBool
var delay, maxDownloads, logScore sql.NullInt32
@ -306,6 +308,8 @@ func (r *FilterRepo) FindByID(ctx context.Context, filterID int) (*domain.Filter
pq.Array(&f.MatchOther),
pq.Array(&f.ExceptOther),
&years,
&months,
&days,
&artists,
&albums,
pq.Array(&f.MatchReleaseTypes),
@ -361,6 +365,8 @@ func (r *FilterRepo) FindByID(ctx context.Context, filterID int) (*domain.Filter
f.Seasons = seasons.String
f.Episodes = episodes.String
f.Years = years.String
f.Months = months.String
f.Days = days.String
f.Artists = artists.String
f.Albums = albums.String
f.LogScore = int(logScore.Int32)
@ -426,6 +432,8 @@ func (r *FilterRepo) findByIndexerIdentifier(ctx context.Context, indexer string
"f.match_other",
"f.except_other",
"f.years",
"f.months",
"f.days",
"f.artists",
"f.albums",
"f.release_types_match",
@ -480,7 +488,7 @@ func (r *FilterRepo) findByIndexerIdentifier(ctx context.Context, indexer string
for rows.Next() {
var f domain.Filter
var minSize, maxSize, maxDownloadsUnit, matchReleases, exceptReleases, matchReleaseGroups, exceptReleaseGroups, matchReleaseTags, exceptReleaseTags, matchDescription, exceptDescription, freeleechPercent, shows, seasons, episodes, years, artists, albums, matchCategories, exceptCategories, matchUploaders, exceptUploaders, tags, exceptTags, tagsMatchLogic, exceptTagsMatchLogic sql.NullString
var minSize, maxSize, maxDownloadsUnit, matchReleases, exceptReleases, matchReleaseGroups, exceptReleaseGroups, matchReleaseTags, exceptReleaseTags, matchDescription, exceptDescription, freeleechPercent, shows, seasons, episodes, years, months, days, artists, albums, matchCategories, exceptCategories, matchUploaders, exceptUploaders, tags, exceptTags, tagsMatchLogic, exceptTagsMatchLogic sql.NullString
var useRegex, scene, freeleech, hasLog, hasCue, perfectFlac sql.NullBool
var delay, maxDownloads, logScore sql.NullInt32
@ -521,6 +529,8 @@ func (r *FilterRepo) findByIndexerIdentifier(ctx context.Context, indexer string
pq.Array(&f.MatchOther),
pq.Array(&f.ExceptOther),
&years,
&months,
&days,
&artists,
&albums,
pq.Array(&f.MatchReleaseTypes),
@ -572,6 +582,8 @@ func (r *FilterRepo) findByIndexerIdentifier(ctx context.Context, indexer string
f.Seasons = seasons.String
f.Episodes = episodes.String
f.Years = years.String
f.Months = months.String
f.Days = days.String
f.Artists = artists.String
f.Albums = albums.String
f.LogScore = int(logScore.Int32)
@ -722,6 +734,8 @@ func (r *FilterRepo) Store(ctx context.Context, filter *domain.Filter) error {
"match_other",
"except_other",
"years",
"months",
"days",
"match_categories",
"except_categories",
"match_uploaders",
@ -785,6 +799,8 @@ func (r *FilterRepo) Store(ctx context.Context, filter *domain.Filter) error {
pq.Array(filter.MatchOther),
pq.Array(filter.ExceptOther),
filter.Years,
filter.Months,
filter.Days,
filter.MatchCategories,
filter.ExceptCategories,
filter.MatchUploaders,
@ -866,6 +882,8 @@ func (r *FilterRepo) Update(ctx context.Context, filter *domain.Filter) error {
Set("match_other", pq.Array(filter.MatchOther)).
Set("except_other", pq.Array(filter.ExceptOther)).
Set("years", filter.Years).
Set("months", filter.Months).
Set("days", filter.Days).
Set("match_categories", filter.MatchCategories).
Set("except_categories", filter.ExceptCategories).
Set("match_uploaders", filter.MatchUploaders).
@ -1024,6 +1042,12 @@ func (r *FilterRepo) UpdatePartial(ctx context.Context, filter domain.FilterUpda
if filter.Years != nil {
q = q.Set("years", filter.Years)
}
if filter.Months != nil {
q = q.Set("months", filter.Months)
}
if filter.Days != nil {
q = q.Set("days", filter.Days)
}
if filter.MatchCategories != nil {
q = q.Set("match_categories", filter.MatchCategories)
}

View file

@ -52,6 +52,8 @@ func getMockFilter() *domain.Filter {
MatchOther: []string{"Atmos"},
ExceptOther: []string{"Atmos"},
Years: "2023",
Months: "",
Days: "",
Artists: "",
Albums: "",
MatchReleaseTypes: []string{"Remux"},

View file

@ -106,6 +106,8 @@ CREATE TABLE filter
match_other TEXT [] DEFAULT '{}',
except_other TEXT [] DEFAULT '{}',
years TEXT,
months TEXT,
days TEXT,
artists TEXT,
albums TEXT,
release_types_match TEXT [] DEFAULT '{}',
@ -245,6 +247,8 @@ CREATE TABLE "release"
season INTEGER,
episode INTEGER,
year INTEGER,
month INTEGER,
day INTEGER,
resolution TEXT,
source TEXT,
codec TEXT,
@ -884,5 +888,17 @@ ADD COLUMN first_last_piece_prio BOOLEAN DEFAULT false;
UPDATE indexer
SET identifier_external = name;
`,
`ALTER TABLE "release"
ADD COLUMN month INTEGER;
ALTER TABLE "release"
ADD COLUMN day INTEGER;
ALTER TABLE filter
ADD COLUMN months TEXT;
ALTER TABLE filter
ADD COLUMN days TEXT;
`,
}

View file

@ -38,8 +38,8 @@ func (repo *ReleaseRepo) Store(ctx context.Context, r *domain.Release) error {
queryBuilder := repo.db.squirrel.
Insert("release").
Columns("filter_status", "rejections", "indexer", "filter", "protocol", "implementation", "timestamp", "group_id", "torrent_id", "info_url", "download_url", "torrent_name", "size", "title", "category", "season", "episode", "year", "resolution", "source", "codec", "container", "hdr", "release_group", "proper", "repack", "website", "type", "origin", "tags", "uploader", "pre_time", "filter_id").
Values(r.FilterStatus, pq.Array(r.Rejections), r.Indexer.Identifier, r.FilterName, r.Protocol, r.Implementation, r.Timestamp.Format(time.RFC3339), r.GroupID, r.TorrentID, r.InfoURL, r.DownloadURL, r.TorrentName, r.Size, r.Title, r.Category, r.Season, r.Episode, r.Year, r.Resolution, r.Source, codecStr, r.Container, hdrStr, r.Group, r.Proper, r.Repack, r.Website, r.Type, r.Origin, pq.Array(r.Tags), r.Uploader, r.PreTime, r.FilterID).
Columns("filter_status", "rejections", "indexer", "filter", "protocol", "implementation", "timestamp", "group_id", "torrent_id", "info_url", "download_url", "torrent_name", "size", "title", "category", "season", "episode", "year", "month", "day", "resolution", "source", "codec", "container", "hdr", "release_group", "proper", "repack", "website", "type", "origin", "tags", "uploader", "pre_time", "filter_id").
Values(r.FilterStatus, pq.Array(r.Rejections), r.Indexer.Identifier, r.FilterName, r.Protocol, r.Implementation, r.Timestamp.Format(time.RFC3339), r.GroupID, r.TorrentID, r.InfoURL, r.DownloadURL, r.TorrentName, r.Size, r.Title, r.Category, r.Season, r.Episode, r.Year, r.Month, r.Day, r.Resolution, r.Source, codecStr, r.Container, hdrStr, r.Group, r.Proper, r.Repack, r.Website, r.Type, r.Origin, pq.Array(r.Tags), r.Uploader, r.PreTime, r.FilterID).
Suffix("RETURNING id").RunWith(repo.db.handler)
// return values
@ -668,44 +668,49 @@ func (repo *ReleaseRepo) Delete(ctx context.Context, req *domain.DeleteReleaseRe
return nil
}
func (repo *ReleaseRepo) CanDownloadShow(ctx context.Context, title string, season int, episode int) (bool, error) {
// TODO support non season episode shows
// if rls.Day > 0 {
// // Maybe in the future
// // SELECT '' FROM release WHERE Title LIKE %q AND ((Year == %d AND Month == %d AND Day > %d) OR (Year == %d AND Month > %d) OR (Year > %d))"
// qs := sql.Query("SELECT torrent_name FROM release WHERE Title LIKE %q AND Year >= %d", rls.Title, rls.Year)
//
// for q := range qs.Rows() {
// r := rls.ParseTitle(q)
// if r.Year > rls.Year {
// return false, fmt.Errorf("stale release year")
// }
//
// if r.Month > rls.Month {
// return false, fmt.Errorf("stale release month")
// }
//
// if r.Month == rls.Month && r.Day > rls.Day {
// return false, fmt.Errorf("stale release day")
// }
// }
//}
func (repo *ReleaseRepo) CheckSmartEpisodeCanDownload(ctx context.Context, p *domain.SmartEpisodeParams) (bool, error) {
queryBuilder := repo.db.squirrel.
Select("COUNT(*)").
From("release").
Where(repo.db.ILike("title", title+"%"))
From("release r").
LeftJoin("release_action_status ras ON r.id = ras.release_id").
Where(sq.And{
repo.db.ILike("r.title", p.Title+"%"),
sq.Eq{"ras.status": "PUSH_APPROVED"},
})
if season > 0 && episode > 0 {
if p.Proper {
queryBuilder = queryBuilder.Where(sq.Eq{"r.proper": p.Proper})
}
if p.Repack {
queryBuilder = queryBuilder.Where(sq.And{
sq.Eq{"r.repack": p.Repack},
repo.db.ILike("r.release_group", p.Group),
})
}
if p.Season > 0 && p.Episode > 0 {
queryBuilder = queryBuilder.Where(sq.Or{
sq.And{
sq.Eq{"season": season},
sq.Gt{"episode": episode},
sq.Eq{"r.season": p.Season},
sq.Gt{"r.episode": p.Episode},
},
sq.Gt{"season": season},
sq.Gt{"r.season": p.Season},
})
} else if p.Season > 0 && p.Episode == 0 {
queryBuilder = queryBuilder.Where(sq.Gt{"r.season": p.Season})
} else if p.Year > 0 && p.Month > 0 && p.Day > 0 {
queryBuilder = queryBuilder.Where(sq.Or{
sq.And{
sq.Eq{"r.year": p.Year},
sq.Eq{"r.month": p.Month},
sq.Gt{"r.day": p.Day},
},
sq.And{
sq.Eq{"r.year": p.Year},
sq.Gt{"r.month": p.Month},
},
sq.Gt{"r.year": p.Year},
})
} else if season > 0 && episode == 0 {
queryBuilder = queryBuilder.Where(sq.Gt{"season": season})
} else {
/* No support for this scenario today. Specifically multi-part specials.
* The Database presently does not have Subtitle as a field, but is coming at a future date. */
@ -717,6 +722,8 @@ func (repo *ReleaseRepo) CanDownloadShow(ctx context.Context, title string, seas
return false, errors.Wrap(err, "error building query")
}
repo.log.Trace().Str("method", "CheckSmartEpisodeCanDownload").Str("query", query).Interface("args", args).Msgf("executing query")
row := repo.db.handler.QueryRowContext(ctx, query, args...)
if err := row.Err(); err != nil {
return false, err

View file

@ -582,7 +582,7 @@ func TestReleaseRepo_Delete(t *testing.T) {
}
}
func TestReleaseRepo_CanDownloadShow(t *testing.T) {
func TestReleaseRepo_CheckSmartEpisodeCanDownloadShow(t *testing.T) {
for dbType, db := range testDBs {
log := setupLoggerForTest()
@ -595,7 +595,7 @@ func TestReleaseRepo_CanDownloadShow(t *testing.T) {
releaseActionMockData := getMockReleaseActionStatus()
actionMockData := getMockAction()
t.Run(fmt.Sprintf("Delete_Succeeds [%s]", dbType), func(t *testing.T) {
t.Run(fmt.Sprintf("Check_Smart_Episode_Can_Download [%s]", dbType), func(t *testing.T) {
// Setup
createdClient, err := downloadClientRepo.Store(context.Background(), getMockDownloadClient())
assert.NoError(t, err)
@ -624,8 +624,17 @@ func TestReleaseRepo_CanDownloadShow(t *testing.T) {
err = repo.StoreReleaseActionStatus(context.Background(), releaseActionMockData)
assert.NoError(t, err)
params := &domain.SmartEpisodeParams{
Title: "Example.Torrent.Name",
Season: 1,
Episode: 2,
Year: 0,
Month: 0,
Day: 0,
}
// Execute
canDownload, err := repo.CanDownloadShow(context.Background(), "Example.Torrent.Name", 1, 2)
canDownload, err := repo.CheckSmartEpisodeCanDownload(context.Background(), params)
// Verify
assert.NoError(t, err)

View file

@ -106,6 +106,8 @@ CREATE TABLE filter
match_other TEXT [] DEFAULT '{}',
except_other TEXT [] DEFAULT '{}',
years TEXT,
months TEXT,
days TEXT,
artists TEXT,
albums TEXT,
release_types_match TEXT [] DEFAULT '{}',
@ -244,6 +246,8 @@ CREATE TABLE "release"
season INTEGER,
episode INTEGER,
year INTEGER,
month INTEGER,
day INTEGER,
resolution TEXT,
source TEXT,
codec TEXT,
@ -1522,5 +1526,17 @@ ALTER TABLE filter
UPDATE indexer
SET identifier_external = name;
`,
`ALTER TABLE "release"
ADD COLUMN month INTEGER;
ALTER TABLE "release"
ADD COLUMN day INTEGER;
ALTER TABLE filter
ADD COLUMN months TEXT;
ALTER TABLE filter
ADD COLUMN days TEXT;
`,
}

View file

@ -62,6 +62,22 @@ const (
FilterMaxDownloadsEver FilterMaxDownloadsUnit = "EVER"
)
type SmartEpisodeParams struct {
Title string
Season int
Episode int
Year int
Month int
Day int
Repack bool
Proper bool
Group string
}
func (p *SmartEpisodeParams) IsDailyEpisode() bool {
return p.Year != 0 && p.Month != 0 && p.Day != 0
}
type FilterQueryParams struct {
Sort map[string]string
Filters struct {
@ -106,6 +122,8 @@ type Filter struct {
MatchOther []string `json:"match_other,omitempty"`
ExceptOther []string `json:"except_other,omitempty"`
Years string `json:"years,omitempty"`
Months string `json:"months,omitempty"`
Days string `json:"days,omitempty"`
Artists string `json:"artists,omitempty"`
Albums string `json:"albums,omitempty"`
MatchReleaseTypes []string `json:"match_release_types,omitempty"` // Album,Single,EP
@ -215,6 +233,8 @@ type FilterUpdate struct {
MatchOther *[]string `json:"match_other,omitempty"`
ExceptOther *[]string `json:"except_other,omitempty"`
Years *string `json:"years,omitempty"`
Months *string `json:"months,omitempty"`
Days *string `json:"days,omitempty"`
Artists *string `json:"artists,omitempty"`
Albums *string `json:"albums,omitempty"`
MatchReleaseTypes *[]string `json:"match_release_types,omitempty"` // Album,Single,EP
@ -309,6 +329,8 @@ func (f *Filter) Sanitize() error {
}
f.Years = sanitize.FilterString(f.Years)
f.Months = sanitize.FilterString(f.Months)
f.Days = sanitize.FilterString(f.Days)
f.Artists = sanitize.FilterString(f.Artists)
f.Albums = sanitize.FilterString(f.Albums)
@ -460,6 +482,14 @@ func (f *Filter) CheckFilter(r *Release) ([]string, bool) {
f.addRejectionF("year not matching. got: %d want: %v", r.Year, f.Years)
}
if f.Months != "" && !containsIntStrings(r.Month, f.Months) {
f.addRejectionF("month not matching. got: %d want: %v", r.Month, f.Months)
}
if f.Days != "" && !containsIntStrings(r.Day, f.Days) {
f.addRejectionF("day not matching. got: %d want: %v", r.Day, f.Days)
}
if f.MatchCategories != "" {
var categories []string
categories = append(categories, r.Categories...)

View file

@ -1340,6 +1340,44 @@ func TestFilter_CheckFilter(t *testing.T) {
},
want: true,
},
{
name: "match_daily",
fields: &Release{
TorrentName: "Daily talk show 2022 04 20 Someone 1080p WEB-DL h264-GROUP",
Category: "TV",
Uploader: "Uploader1",
},
args: args{
filter: Filter{
Enabled: true,
MatchCategories: "*tv*",
Shows: "Daily talk show",
Years: "2022",
Months: "04",
Days: "20",
},
},
want: true,
},
{
name: "daily_dont_match",
fields: &Release{
TorrentName: "Daily talk show 2022 04 20 Someone 1080p WEB-DL h264-GROUP",
Category: "TV",
Uploader: "Uploader1",
},
args: args{
filter: Filter{
Enabled: true,
MatchCategories: "*tv*",
Shows: "Daily talk show",
Years: "2022",
Months: "05",
},
rejections: []string{"month not matching. got: 4 want: 05"},
},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@ -1911,6 +1949,8 @@ func TestFilter_CheckFilter1(t *testing.T) {
MatchHDR: tt.fields.MatchHDR,
ExceptHDR: tt.fields.ExceptHDR,
Years: tt.fields.Years,
Months: tt.fields.Months,
Days: tt.fields.Days,
Artists: tt.fields.Artists,
Albums: tt.fields.Albums,
MatchReleaseTypes: tt.fields.MatchReleaseTypes,

View file

@ -12,6 +12,7 @@ import (
"net/http"
"net/http/cookiejar"
"os"
"slices"
"strconv"
"strings"
"time"
@ -35,7 +36,7 @@ type ReleaseRepo interface {
GetIndexerOptions(ctx context.Context) ([]string, error)
Stats(ctx context.Context) (*ReleaseStats, error)
Delete(ctx context.Context, req *DeleteReleaseRequest) error
CanDownloadShow(ctx context.Context, title string, season int, episode int) (bool, error)
CheckSmartEpisodeCanDownload(ctx context.Context, p *SmartEpisodeParams) (bool, error)
UpdateBaseURL(ctx context.Context, indexer string, oldBaseURL, newBaseURL string) error
GetActionStatus(ctx context.Context, req *GetReleaseActionStatusRequest) (*ReleaseActionStatus, error)
@ -68,6 +69,8 @@ type Release struct {
Season int `json:"season"`
Episode int `json:"episode"`
Year int `json:"year"`
Month int `json:"month"`
Day int `json:"day"`
Resolution string `json:"resolution"`
Source string `json:"source"`
Codec []string `json:"codec"`
@ -316,10 +319,14 @@ func (r *Release) ParseString(title string) {
r.Codec = rel.Codec
r.Container = rel.Container
r.HDR = rel.HDR
r.Other = rel.Other
r.Artists = rel.Artist
r.Language = rel.Language
r.Other = rel.Other
r.Proper = slices.Contains(r.Other, "PROPER")
r.Repack = slices.Contains(r.Other, "REPACK")
if r.Title == "" {
r.Title = rel.Title
}
@ -334,6 +341,12 @@ func (r *Release) ParseString(title string) {
if r.Year == 0 {
r.Year = rel.Year
}
if r.Month == 0 {
r.Month = rel.Month
}
if r.Day == 0 {
r.Day = rel.Day
}
if r.Group == "" {
r.Group = rel.Group

View file

@ -72,7 +72,7 @@ func TestRSSJob_processItem(t *testing.T) {
Link: "/details.php?id=00000&hit=1",
GUID: "Some.Release.Title.2022.09.22.720p.WEB.h264-GROUP",
}},
want: &domain.Release{ID: 0, FilterStatus: "PENDING", Rejections: []string{}, Indexer: domain.IndexerMinimal{0, "Mock Feed", "mock-feed", "Mock Indexer"}, FilterName: "", Protocol: "torrent", Implementation: "RSS", Timestamp: now, GroupID: "", TorrentID: "", DownloadURL: "https://fake-feed.com/details.php?id=00000&hit=1", TorrentTmpFile: "", TorrentDataRawBytes: []uint8(nil), TorrentHash: "", TorrentName: "Some.Release.Title.2022.09.22.720p.WEB.h264-GROUP", Size: 1490000000, Title: "Some Release Title", Description: "Category: Example\n Size: 1.49 GB\n Status: 27 seeders and 1 leechers\n Speed: 772.16 kB/s\n Added: 2022-09-29 16:06:08\n", Category: "", Season: 0, Episode: 0, Year: 2022, Resolution: "720p", Source: "WEB", Codec: []string{"H.264"}, Container: "", HDR: []string(nil), Audio: []string(nil), AudioChannels: "", Group: "GROUP", Region: "", Language: nil, Proper: false, Repack: false, Website: "", Artists: "", Type: "episode", LogScore: 0, Origin: "", Tags: []string{}, ReleaseTags: "", Freeleech: false, FreeleechPercent: 0, Bonus: []string(nil), Uploader: "", PreTime: "", Other: []string(nil), RawCookie: "", AdditionalSizeCheckRequired: false, FilterID: 0, Filter: (*domain.Filter)(nil), ActionStatus: []domain.ReleaseActionStatus(nil)},
want: &domain.Release{ID: 0, FilterStatus: "PENDING", Rejections: []string{}, Indexer: domain.IndexerMinimal{0, "Mock Feed", "mock-feed", "Mock Indexer"}, FilterName: "", Protocol: "torrent", Implementation: "RSS", Timestamp: now, GroupID: "", TorrentID: "", DownloadURL: "https://fake-feed.com/details.php?id=00000&hit=1", TorrentTmpFile: "", TorrentDataRawBytes: []uint8(nil), TorrentHash: "", TorrentName: "Some.Release.Title.2022.09.22.720p.WEB.h264-GROUP", Size: 1490000000, Title: "Some Release Title", Description: "Category: Example\n Size: 1.49 GB\n Status: 27 seeders and 1 leechers\n Speed: 772.16 kB/s\n Added: 2022-09-29 16:06:08\n", Category: "", Season: 0, Episode: 0, Year: 2022, Month: 9, Day: 22, Resolution: "720p", Source: "WEB", Codec: []string{"H.264"}, Container: "", HDR: []string(nil), Audio: []string(nil), AudioChannels: "", Group: "GROUP", Region: "", Language: nil, Proper: false, Repack: false, Website: "", Artists: "", Type: "episode", LogScore: 0, Origin: "", Tags: []string{}, ReleaseTags: "", Freeleech: false, FreeleechPercent: 0, Bonus: []string(nil), Uploader: "", PreTime: "", Other: []string(nil), RawCookie: "", AdditionalSizeCheckRequired: false, FilterID: 0, Filter: (*domain.Filter)(nil), ActionStatus: []domain.ReleaseActionStatus(nil)},
},
{
name: "with_baseurl",
@ -106,7 +106,7 @@ func TestRSSJob_processItem(t *testing.T) {
Link: "https://fake-feed.com/details.php?id=00000&hit=1",
GUID: "Some.Release.Title.2022.09.22.720p.WEB.h264-GROUP",
}},
want: &domain.Release{ID: 0, FilterStatus: "PENDING", Rejections: []string{}, Indexer: domain.IndexerMinimal{0, "Mock Feed", "mock-feed", "Mock Indexer"}, FilterName: "", Protocol: "torrent", Implementation: "RSS", Timestamp: now, GroupID: "", TorrentID: "", DownloadURL: "https://fake-feed.com/details.php?id=00000&hit=1", TorrentTmpFile: "", TorrentDataRawBytes: []uint8(nil), TorrentHash: "", TorrentName: "Some.Release.Title.2022.09.22.720p.WEB.h264-GROUP", Size: 1490000000, Title: "Some Release Title", Description: "Category: Example\n Size: 1.49 GB\n Status: 27 seeders and 1 leechers\n Speed: 772.16 kB/s\n Added: 2022-09-29 16:06:08\n", Category: "", Season: 0, Episode: 0, Year: 2022, Resolution: "720p", Source: "WEB", Codec: []string{"H.264"}, Container: "", HDR: []string(nil), Audio: []string(nil), AudioChannels: "", Group: "GROUP", Region: "", Language: nil, Proper: false, Repack: false, Website: "", Artists: "", Type: "episode", LogScore: 0, Origin: "", Tags: []string{}, ReleaseTags: "", Freeleech: false, FreeleechPercent: 0, Bonus: []string(nil), Uploader: "", PreTime: "", Other: []string(nil), RawCookie: "", AdditionalSizeCheckRequired: false, FilterID: 0, Filter: (*domain.Filter)(nil), ActionStatus: []domain.ReleaseActionStatus(nil)},
want: &domain.Release{ID: 0, FilterStatus: "PENDING", Rejections: []string{}, Indexer: domain.IndexerMinimal{0, "Mock Feed", "mock-feed", "Mock Indexer"}, FilterName: "", Protocol: "torrent", Implementation: "RSS", Timestamp: now, GroupID: "", TorrentID: "", DownloadURL: "https://fake-feed.com/details.php?id=00000&hit=1", TorrentTmpFile: "", TorrentDataRawBytes: []uint8(nil), TorrentHash: "", TorrentName: "Some.Release.Title.2022.09.22.720p.WEB.h264-GROUP", Size: 1490000000, Title: "Some Release Title", Description: "Category: Example\n Size: 1.49 GB\n Status: 27 seeders and 1 leechers\n Speed: 772.16 kB/s\n Added: 2022-09-29 16:06:08\n", Category: "", Season: 0, Episode: 0, Year: 2022, Month: 9, Day: 22, Resolution: "720p", Source: "WEB", Codec: []string{"H.264"}, Container: "", HDR: []string(nil), Audio: []string(nil), AudioChannels: "", Group: "GROUP", Region: "", Language: nil, Proper: false, Repack: false, Website: "", Artists: "", Type: "episode", LogScore: 0, Origin: "", Tags: []string{}, ReleaseTags: "", Freeleech: false, FreeleechPercent: 0, Bonus: []string(nil), Uploader: "", PreTime: "", Other: []string(nil), RawCookie: "", AdditionalSizeCheckRequired: false, FilterID: 0, Filter: (*domain.Filter)(nil), ActionStatus: []domain.ReleaseActionStatus(nil)},
},
{
name: "time_parse",
@ -141,7 +141,7 @@ func TestRSSJob_processItem(t *testing.T) {
GUID: "Some.Release.Title.2022.09.22.720p.WEB.h264-GROUP",
//PublishedParsed: &nowMinusTime,
}},
want: &domain.Release{ID: 0, FilterStatus: "PENDING", Rejections: []string{}, Indexer: domain.IndexerMinimal{0, "Mock Feed", "mock-feed", "Mock Indexer"}, FilterName: "", Protocol: "torrent", Implementation: "RSS", Timestamp: now, GroupID: "", TorrentID: "", DownloadURL: "https://fake-feed.com/details.php?id=00000&hit=1", TorrentTmpFile: "", TorrentDataRawBytes: []uint8(nil), TorrentHash: "", TorrentName: "Some.Release.Title.2022.09.22.720p.WEB.h264-GROUP", Size: 1490000000, Title: "Some Release Title", Description: "Category: Example\n Size: 1.49 GB\n Status: 27 seeders and 1 leechers\n Speed: 772.16 kB/s\n Added: 2022-09-29 16:06:08\n", Category: "", Season: 0, Episode: 0, Year: 2022, Resolution: "720p", Source: "WEB", Codec: []string{"H.264"}, Container: "", HDR: []string(nil), Audio: []string(nil), AudioChannels: "", Group: "GROUP", Region: "", Language: nil, Proper: false, Repack: false, Website: "", Artists: "", Type: "episode", LogScore: 0, Origin: "", Tags: []string{}, ReleaseTags: "", Freeleech: false, FreeleechPercent: 0, Bonus: []string(nil), Uploader: "", PreTime: "", Other: []string(nil), RawCookie: "", AdditionalSizeCheckRequired: false, FilterID: 0, Filter: (*domain.Filter)(nil), ActionStatus: []domain.ReleaseActionStatus(nil)},
want: &domain.Release{ID: 0, FilterStatus: "PENDING", Rejections: []string{}, Indexer: domain.IndexerMinimal{0, "Mock Feed", "mock-feed", "Mock Indexer"}, FilterName: "", Protocol: "torrent", Implementation: "RSS", Timestamp: now, GroupID: "", TorrentID: "", DownloadURL: "https://fake-feed.com/details.php?id=00000&hit=1", TorrentTmpFile: "", TorrentDataRawBytes: []uint8(nil), TorrentHash: "", TorrentName: "Some.Release.Title.2022.09.22.720p.WEB.h264-GROUP", Size: 1490000000, Title: "Some Release Title", Description: "Category: Example\n Size: 1.49 GB\n Status: 27 seeders and 1 leechers\n Speed: 772.16 kB/s\n Added: 2022-09-29 16:06:08\n", Category: "", Season: 0, Episode: 0, Year: 2022, Month: 9, Day: 22, Resolution: "720p", Source: "WEB", Codec: []string{"H.264"}, Container: "", HDR: []string(nil), Audio: []string(nil), AudioChannels: "", Group: "GROUP", Region: "", Language: nil, Proper: false, Repack: false, Website: "", Artists: "", Type: "episode", LogScore: 0, Origin: "", Tags: []string{}, ReleaseTags: "", Freeleech: false, FreeleechPercent: 0, Bonus: []string(nil), Uploader: "", PreTime: "", Other: []string(nil), RawCookie: "", AdditionalSizeCheckRequired: false, FilterID: 0, Filter: (*domain.Filter)(nil), ActionStatus: []domain.ReleaseActionStatus(nil)},
},
{
name: "time_parse",
@ -207,7 +207,7 @@ func TestRSSJob_processItem(t *testing.T) {
},
},
}},
want: &domain.Release{ID: 0, FilterStatus: "PENDING", Rejections: []string{}, Indexer: domain.IndexerMinimal{0, "Mock Feed", "mock-feed", "Mock Indexer"}, FilterName: "", Protocol: "torrent", Implementation: "RSS", Timestamp: now, MagnetURI: "magnet:?xt=this-not-a-valid-magnet", GroupID: "", TorrentID: "", DownloadURL: "https://fake-feed.com/details.php?id=00000&hit=1", TorrentTmpFile: "", TorrentDataRawBytes: []uint8(nil), TorrentHash: "", TorrentName: "Some.Release.Title.2022.09.22.720p.WEB.h264-GROUP", Size: 0, Title: "Some Release Title", Description: "Category: Example", Category: "", Season: 0, Episode: 0, Year: 2022, Resolution: "720p", Source: "WEB", Codec: []string{"H.264"}, Container: "", HDR: []string(nil), Audio: []string(nil), AudioChannels: "", Group: "GROUP", Region: "", Language: nil, Proper: false, Repack: false, Website: "", Artists: "", Type: "episode", LogScore: 0, Origin: "", Tags: []string{}, ReleaseTags: "", Freeleech: false, FreeleechPercent: 0, Bonus: []string(nil), Uploader: "", PreTime: "", Other: []string(nil), RawCookie: "", AdditionalSizeCheckRequired: false, FilterID: 0, Filter: (*domain.Filter)(nil), ActionStatus: []domain.ReleaseActionStatus(nil)},
want: &domain.Release{ID: 0, FilterStatus: "PENDING", Rejections: []string{}, Indexer: domain.IndexerMinimal{0, "Mock Feed", "mock-feed", "Mock Indexer"}, FilterName: "", Protocol: "torrent", Implementation: "RSS", Timestamp: now, MagnetURI: "magnet:?xt=this-not-a-valid-magnet", GroupID: "", TorrentID: "", DownloadURL: "https://fake-feed.com/details.php?id=00000&hit=1", TorrentTmpFile: "", TorrentDataRawBytes: []uint8(nil), TorrentHash: "", TorrentName: "Some.Release.Title.2022.09.22.720p.WEB.h264-GROUP", Size: 0, Title: "Some Release Title", Description: "Category: Example", Category: "", Season: 0, Episode: 0, Year: 2022, Month: 9, Day: 22, Resolution: "720p", Source: "WEB", Codec: []string{"H.264"}, Container: "", HDR: []string(nil), Audio: []string(nil), AudioChannels: "", Group: "GROUP", Region: "", Language: nil, Proper: false, Repack: false, Website: "", Artists: "", Type: "episode", LogScore: 0, Origin: "", Tags: []string{}, ReleaseTags: "", Freeleech: false, FreeleechPercent: 0, Bonus: []string(nil), Uploader: "", PreTime: "", Other: []string(nil), RawCookie: "", AdditionalSizeCheckRequired: false, FilterID: 0, Filter: (*domain.Filter)(nil), ActionStatus: []domain.ReleaseActionStatus(nil)},
},
}
for _, tt := range tests {

View file

@ -41,7 +41,7 @@ type Service interface {
ToggleEnabled(ctx context.Context, filterID int, enabled bool) error
Delete(ctx context.Context, filterID int) error
AdditionalSizeCheck(ctx context.Context, f *domain.Filter, release *domain.Release) (bool, error)
CanDownloadShow(ctx context.Context, release *domain.Release) (bool, error)
CheckSmartEpisodeCanDownload(ctx context.Context, params *domain.SmartEpisodeParams) (bool, error)
GetDownloadsByFilterId(ctx context.Context, filterID int) (*domain.FilterDownloads, error)
}
@ -391,7 +391,18 @@ func (s *service) CheckFilter(ctx context.Context, f *domain.Filter, release *do
if matchedFilter {
// smartEpisode check
if f.SmartEpisode {
canDownloadShow, err := s.CanDownloadShow(ctx, release)
params := &domain.SmartEpisodeParams{
Title: release.Title,
Season: release.Season,
Episode: release.Episode,
Year: release.Year,
Month: release.Month,
Day: release.Day,
Repack: release.Repack,
Proper: release.Proper,
Group: release.Group,
}
canDownloadShow, err := s.CheckSmartEpisodeCanDownload(ctx, params)
if err != nil {
l.Trace().Msgf("failed smart episode check: %s", f.Name)
return false, nil
@ -399,7 +410,13 @@ func (s *service) CheckFilter(ctx context.Context, f *domain.Filter, release *do
if !canDownloadShow {
l.Trace().Msgf("failed smart episode check: %s", f.Name)
release.AddRejectionF("smart episode check: not new: (%s) season: %d ep: %d", release.Title, release.Season, release.Episode)
if params.IsDailyEpisode() {
f.AddRejectionF("smart episode check: not new: (%s) Daily: %d-%d-%d", release.Title, release.Year, release.Month, release.Day)
} else {
f.AddRejectionF("smart episode check: not new: (%s) season: %d ep: %d", release.Title, release.Season, release.Episode)
}
return false, nil
}
}
@ -506,8 +523,8 @@ func (s *service) AdditionalSizeCheck(ctx context.Context, f *domain.Filter, rel
return true, nil
}
func (s *service) CanDownloadShow(ctx context.Context, release *domain.Release) (bool, error) {
return s.releaseRepo.CanDownloadShow(ctx, release.Title, release.Season, release.Episode)
func (s *service) CheckSmartEpisodeCanDownload(ctx context.Context, params *domain.SmartEpisodeParams) (bool, error) {
return s.releaseRepo.CheckSmartEpisodeCanDownload(ctx, params)
}
func (s *service) RunExternalFilters(ctx context.Context, f *domain.Filter, externalFilters []domain.FilterExternal, release *domain.Release) (bool, error) {

View file

@ -11,7 +11,7 @@ interface Props {
export const TitleSubtitle = ({ title, subtitle, className }: Props) => (
<div className={className}>
<h2 className="text-lg leading-5 capitalize font-bold text-gray-900 dark:text-gray-100">{title}</h2>
<h2 className="text-lg leading-5 font-bold text-gray-900 dark:text-gray-100">{title}</h2>
<p className="mt-0.5 text-sm text-gray-500 dark:text-gray-400">{subtitle}</p>
</div>
);

View file

@ -387,6 +387,8 @@ export const FilterDetails = () => {
use_regex: filter.use_regex || false,
shows: filter.shows,
years: filter.years,
months: filter.months,
days: filter.days,
resolutions: filter.resolutions || [],
sources: filter.sources || [],
codecs: filter.codecs || [],

View file

@ -12,14 +12,14 @@ import * as Components from "./_components";
const SeasonsAndEpisodes = () => (
<Components.Section
title="Seasons and Episodes"
subtitle="Set season and episode match constraints."
title="Seasons, Episodes and Date"
subtitle="Set season, episode, year, months and day match constraints."
>
<Components.Layout>
<TextField
name="seasons"
label="Seasons"
columns={8}
columns={6}
placeholder="eg. 1,3,2-6"
tooltip={
<div>
@ -31,16 +31,52 @@ const SeasonsAndEpisodes = () => (
<TextField
name="episodes"
label="Episodes"
columns={4}
columns={6}
placeholder="eg. 2,4,10-20"
tooltip={
<div>
<p>See docs for information about how to <b>only</b> grab episodes:</p>
<DocsLink href="https://autobrr.com/filters/examples#only-episodes-skip-season-packs" />
<DocsLink href="https://autobrr.com/filters/examples#only-episodes-skip-season-packs"/>
</div>
}
/>
<p className="col-span-12 -mb-1 text-sm font-bold text-gray-800 dark:text-gray-100 tracking-wide">Daily Shows</p>
<TextField
name="years"
label="Years"
columns={4}
placeholder="eg. 2018,2019-2021"
tooltip={
<div>
<p>This field takes a range of years and/or comma separated single years.</p>
<DocsLink href="https://autobrr.com/filters#tvmovies"/>
</div>
}
/>
<TextField
name="months"
label="Months"
columns={4}
placeholder="eg. 4,2-9"
tooltip={
<div>
<p>This field takes a range of years and/or comma separated single months.</p>
<DocsLink href="https://autobrr.com/filters#tvmovies"/>
</div>
}
/>
<TextField
name="days"
label="Days"
columns={4}
placeholder="eg. 1,15-30"
tooltip={
<div>
<p>This field takes a range of years and/or comma separated single days.</p>
<DocsLink href="https://autobrr.com/filters#tvmovies"/>
</div>
}
/>
<div className="col-span-12 sm:col-span-6">
<SwitchGroup
name="smart_episode"

View file

@ -44,6 +44,8 @@ interface Filter {
match_other: string[];
except_other: string[];
years: string;
months: string;
days: string;
artists: string;
albums: string;
match_release_types: string[];