From 4fceccd6115db00748b9e42cffefa3260f6c83ec Mon Sep 17 00:00:00 2001 From: kenstir Date: Wed, 15 May 2024 10:38:10 -0400 Subject: [PATCH] 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 Co-authored-by: ze0s <43699394+zze0s@users.noreply.github.com> Co-authored-by: martylukyy <35452459+martylukyy@users.noreply.github.com> --- internal/database/filter.go | 28 ++++++- internal/database/filter_test.go | 2 + internal/database/postgres_migrate.go | 16 ++++ internal/database/release.go | 73 ++++++++++--------- internal/database/release_test.go | 15 +++- internal/database/sqlite_migrate.go | 16 ++++ internal/domain/filter.go | 30 ++++++++ internal/domain/filter_test.go | 40 ++++++++++ internal/domain/release.go | 17 ++++- internal/feed/rss_test.go | 8 +- internal/filter/service.go | 27 +++++-- web/src/components/headings/index.tsx | 2 +- web/src/screens/filters/Details.tsx | 2 + .../screens/filters/sections/MoviesAndTV.tsx | 48 ++++++++++-- web/src/types/Filter.d.ts | 2 + 15 files changed, 270 insertions(+), 56 deletions(-) diff --git a/internal/database/filter.go b/internal/database/filter.go index 47e043e..c0ca5dc 100644 --- a/internal/database/filter.go +++ b/internal/database/filter.go @@ -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) } diff --git a/internal/database/filter_test.go b/internal/database/filter_test.go index aa4a3da..6dade3c 100644 --- a/internal/database/filter_test.go +++ b/internal/database/filter_test.go @@ -52,6 +52,8 @@ func getMockFilter() *domain.Filter { MatchOther: []string{"Atmos"}, ExceptOther: []string{"Atmos"}, Years: "2023", + Months: "", + Days: "", Artists: "", Albums: "", MatchReleaseTypes: []string{"Remux"}, diff --git a/internal/database/postgres_migrate.go b/internal/database/postgres_migrate.go index 899e08b..cf2d51c 100644 --- a/internal/database/postgres_migrate.go +++ b/internal/database/postgres_migrate.go @@ -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; `, } diff --git a/internal/database/release.go b/internal/database/release.go index d007826..f34c713 100644 --- a/internal/database/release.go +++ b/internal/database/release.go @@ -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 diff --git a/internal/database/release_test.go b/internal/database/release_test.go index f55425f..83668ae 100644 --- a/internal/database/release_test.go +++ b/internal/database/release_test.go @@ -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) diff --git a/internal/database/sqlite_migrate.go b/internal/database/sqlite_migrate.go index 6cd8eb9..79cad4f 100644 --- a/internal/database/sqlite_migrate.go +++ b/internal/database/sqlite_migrate.go @@ -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; `, } diff --git a/internal/domain/filter.go b/internal/domain/filter.go index 6ef1335..9c5783b 100644 --- a/internal/domain/filter.go +++ b/internal/domain/filter.go @@ -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...) diff --git a/internal/domain/filter_test.go b/internal/domain/filter_test.go index a530b82..8d58129 100644 --- a/internal/domain/filter_test.go +++ b/internal/domain/filter_test.go @@ -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, diff --git a/internal/domain/release.go b/internal/domain/release.go index 489299f..01bf52a 100644 --- a/internal/domain/release.go +++ b/internal/domain/release.go @@ -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 diff --git a/internal/feed/rss_test.go b/internal/feed/rss_test.go index 208665f..d055564 100644 --- a/internal/feed/rss_test.go +++ b/internal/feed/rss_test.go @@ -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 { diff --git a/internal/filter/service.go b/internal/filter/service.go index 9950866..ff3e818 100644 --- a/internal/filter/service.go +++ b/internal/filter/service.go @@ -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) { diff --git a/web/src/components/headings/index.tsx b/web/src/components/headings/index.tsx index 91f13e3..ecce49b 100644 --- a/web/src/components/headings/index.tsx +++ b/web/src/components/headings/index.tsx @@ -11,7 +11,7 @@ interface Props { export const TitleSubtitle = ({ title, subtitle, className }: Props) => (
-

{title}

+

{title}

{subtitle}

); diff --git a/web/src/screens/filters/Details.tsx b/web/src/screens/filters/Details.tsx index 16baf9a..9ca7c60 100644 --- a/web/src/screens/filters/Details.tsx +++ b/web/src/screens/filters/Details.tsx @@ -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 || [], diff --git a/web/src/screens/filters/sections/MoviesAndTV.tsx b/web/src/screens/filters/sections/MoviesAndTV.tsx index 0aa4a65..4190e24 100644 --- a/web/src/screens/filters/sections/MoviesAndTV.tsx +++ b/web/src/screens/filters/sections/MoviesAndTV.tsx @@ -12,14 +12,14 @@ import * as Components from "./_components"; const SeasonsAndEpisodes = () => ( @@ -31,16 +31,52 @@ const SeasonsAndEpisodes = () => (

See docs for information about how to only grab episodes:

- + + + } + /> +

Daily Shows

+ +

This field takes a range of years and/or comma separated single years.

+ + + } + /> + +

This field takes a range of years and/or comma separated single months.

+ + + } + /> + +

This field takes a range of years and/or comma separated single days.

+ } /> -