diff --git a/internal/domain/filter.go b/internal/domain/filter.go index 36b87af..f03f441 100644 --- a/internal/domain/filter.go +++ b/internal/domain/filter.go @@ -460,12 +460,8 @@ func (f *Filter) CheckFilter(r *Release) (*RejectionReasons, bool) { } } - if f.MatchUploaders != "" && !contains(r.Uploader, f.MatchUploaders) { - f.RejectReasons.Add("match uploaders", r.Uploader, f.MatchUploaders) - } - - if f.ExceptUploaders != "" && contains(r.Uploader, f.ExceptUploaders) { - f.RejectReasons.Add("except uploaders", r.Uploader, f.ExceptUploaders) + if (f.MatchUploaders != "" || f.ExceptUploaders != "") && !f.checkUploader(r) { + // f.checkUploader sets the rejections } if len(f.MatchLanguage) > 0 && !sliceContainsSlice(r.Language, f.MatchLanguage) { @@ -731,6 +727,27 @@ func (f *Filter) checkSizeFilter(r *Release) bool { return true } +// checkUploader checks if the uploader is within the given list. +// if the haystack is not empty but the uploader is, then a further +// investigation is needed +func (f *Filter) checkUploader(r *Release) bool { + // only support additional uploader check for RED and OPS + if r.Uploader == "" && (r.Indexer.Identifier == "redacted" || r.Indexer.Identifier == "ops") { + r.AdditionalUploaderCheckRequired = true + return true + } + + if f.MatchUploaders != "" && !contains(r.Uploader, f.MatchUploaders) { + f.RejectReasons.Add("match uploaders", r.Uploader, f.MatchUploaders) + } + + if f.ExceptUploaders != "" && contains(r.Uploader, f.ExceptUploaders) { + f.RejectReasons.Add("except uploaders", r.Uploader, f.ExceptUploaders) + } + + return true +} + // IsPerfectFLAC Perfect is "CD FLAC Cue Log 100% Lossless or 24bit Lossless" func (f *Filter) IsPerfectFLAC(r *Release) ([]string, bool) { rejections := []string{} @@ -1168,6 +1185,20 @@ func (f *Filter) CheckReleaseSize(releaseSize uint64) (bool, error) { return true, nil } +func (f *Filter) CheckUploader(uploader string) (bool, error) { + if f.MatchUploaders != "" && !contains(uploader, f.MatchUploaders) { + f.RejectReasons.Add("match uploader", uploader, f.MatchUploaders) + return false, nil + } + + if f.ExceptUploaders != "" && contains(uploader, f.ExceptUploaders) { + f.RejectReasons.Add("except uploader", uploader, f.ExceptUploaders) + return false, nil + } + + return true, nil +} + // parsedSizeLimits parses filter bytes limits (expressed as a string) into a // uint64 number of bytes. The bounds are returned as *uint64 number of bytes, // with "nil" representing "no limit". We break out filter size limit parsing diff --git a/internal/domain/rejections.go b/internal/domain/rejections.go index 1fef42c..c34003e 100644 --- a/internal/domain/rejections.go +++ b/internal/domain/rejections.go @@ -7,6 +7,7 @@ import ( "encoding/json" "fmt" "strings" + "sync" ) type Rejection struct { @@ -17,6 +18,7 @@ type Rejection struct { } type RejectionReasons struct { + m sync.RWMutex data []Rejection } @@ -31,6 +33,9 @@ func NewRejectionReasons() *RejectionReasons { } func (r *RejectionReasons) String() string { + r.m.RLock() + defer r.m.RUnlock() + if len(r.data) == 0 { return "" } @@ -53,6 +58,9 @@ func (r *RejectionReasons) String() string { } func (r *RejectionReasons) StringTruncated() string { + r.m.RLock() + defer r.m.RUnlock() + if len(r.data) == 0 { return "" } @@ -85,6 +93,9 @@ func (r *RejectionReasons) StringTruncated() string { } func (r *RejectionReasons) WriteString() string { + r.m.RLock() + defer r.m.RUnlock() + var output []string for _, rejection := range r.data { output = append(output, fmt.Sprintf("[%s] not matching: got %v want: %v", rejection.key, rejection.got, rejection.want)) @@ -94,7 +105,10 @@ func (r *RejectionReasons) WriteString() string { } func (r *RejectionReasons) WriteJSON() ([]byte, error) { + r.m.RLock() + defer r.m.RUnlock() var output map[string]string + for _, rejection := range r.data { output[rejection.key] = fmt.Sprintf("[%s] not matching: got %v want: %v", rejection.key, rejection.got, rejection.want) } @@ -103,6 +117,9 @@ func (r *RejectionReasons) WriteJSON() ([]byte, error) { } func (r *RejectionReasons) Add(key string, got any, want any) { + r.m.Lock() + defer r.m.Unlock() + r.data = append(r.data, Rejection{ key: key, got: got, @@ -111,6 +128,9 @@ func (r *RejectionReasons) Add(key string, got any, want any) { } func (r *RejectionReasons) Addf(key string, format string, got any, want any) { + r.m.Lock() + defer r.m.Unlock() + r.data = append(r.data, Rejection{ key: key, format: format, @@ -120,6 +140,9 @@ func (r *RejectionReasons) Addf(key string, format string, got any, want any) { } func (r *RejectionReasons) AddTruncated(key string, got any, want any) { + r.m.Lock() + defer r.m.Unlock() + switch wanted := want.(type) { case string: if len(wanted) > 1024 { @@ -139,5 +162,7 @@ func (r *RejectionReasons) AddTruncated(key string, got any, want any) { // Clear rejections func (r *RejectionReasons) Clear() { + r.m.Lock() + defer r.m.Unlock() r.data = make([]Rejection, 0) } diff --git a/internal/domain/release.go b/internal/domain/release.go index 9f363bf..67b40d8 100644 --- a/internal/domain/release.go +++ b/internal/domain/release.go @@ -46,70 +46,71 @@ type ReleaseRepo interface { } type Release struct { - ID int64 `json:"id"` - FilterStatus ReleaseFilterStatus `json:"filter_status"` - Rejections []string `json:"rejections"` - Indexer IndexerMinimal `json:"indexer"` - FilterName string `json:"filter"` - Protocol ReleaseProtocol `json:"protocol"` - Implementation ReleaseImplementation `json:"implementation"` // irc, rss, api - Timestamp time.Time `json:"timestamp"` - AnnounceType AnnounceType `json:"announce_type"` - InfoURL string `json:"info_url"` - DownloadURL string `json:"download_url"` - MagnetURI string `json:"-"` - GroupID string `json:"group_id"` - TorrentID string `json:"torrent_id"` - TorrentTmpFile string `json:"-"` - TorrentDataRawBytes []byte `json:"-"` - TorrentHash string `json:"-"` - TorrentName string `json:"name"` // full release name - Size uint64 `json:"size"` - Title string `json:"title"` // Parsed title - Description string `json:"-"` - Category string `json:"category"` - Categories []string `json:"categories,omitempty"` - 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"` - Container string `json:"container"` - HDR []string `json:"hdr"` - Audio []string `json:"-"` - AudioChannels string `json:"-"` - AudioFormat string `json:"-"` - Bitrate string `json:"-"` - Group string `json:"group"` - Region string `json:"-"` - Language []string `json:"-"` - Proper bool `json:"proper"` - Repack bool `json:"repack"` - Website string `json:"website"` - Artists string `json:"-"` - Type string `json:"type"` // Album,Single,EP - LogScore int `json:"-"` - HasCue bool `json:"-"` - HasLog bool `json:"-"` - Origin string `json:"origin"` // P2P, Internal - Tags []string `json:"-"` - ReleaseTags string `json:"-"` - Freeleech bool `json:"-"` - FreeleechPercent int `json:"-"` - Bonus []string `json:"-"` - Uploader string `json:"uploader"` - PreTime string `json:"pre_time"` - Other []string `json:"-"` - RawCookie string `json:"-"` - Seeders int `json:"-"` - Leechers int `json:"-"` - AdditionalSizeCheckRequired bool `json:"-"` - FilterID int `json:"-"` - Filter *Filter `json:"-"` - ActionStatus []ReleaseActionStatus `json:"action_status"` + ID int64 `json:"id"` + FilterStatus ReleaseFilterStatus `json:"filter_status"` + Rejections []string `json:"rejections"` + Indexer IndexerMinimal `json:"indexer"` + FilterName string `json:"filter"` + Protocol ReleaseProtocol `json:"protocol"` + Implementation ReleaseImplementation `json:"implementation"` // irc, rss, api + Timestamp time.Time `json:"timestamp"` + AnnounceType AnnounceType `json:"announce_type"` + InfoURL string `json:"info_url"` + DownloadURL string `json:"download_url"` + MagnetURI string `json:"-"` + GroupID string `json:"group_id"` + TorrentID string `json:"torrent_id"` + TorrentTmpFile string `json:"-"` + TorrentDataRawBytes []byte `json:"-"` + TorrentHash string `json:"-"` + TorrentName string `json:"name"` // full release name + Size uint64 `json:"size"` + Title string `json:"title"` // Parsed title + Description string `json:"-"` + Category string `json:"category"` + Categories []string `json:"categories,omitempty"` + 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"` + Container string `json:"container"` + HDR []string `json:"hdr"` + Audio []string `json:"-"` + AudioChannels string `json:"-"` + AudioFormat string `json:"-"` + Bitrate string `json:"-"` + Group string `json:"group"` + Region string `json:"-"` + Language []string `json:"-"` + Proper bool `json:"proper"` + Repack bool `json:"repack"` + Website string `json:"website"` + Artists string `json:"-"` + Type string `json:"type"` // Album,Single,EP + LogScore int `json:"-"` + HasCue bool `json:"-"` + HasLog bool `json:"-"` + Origin string `json:"origin"` // P2P, Internal + Tags []string `json:"-"` + ReleaseTags string `json:"-"` + Freeleech bool `json:"-"` + FreeleechPercent int `json:"-"` + Bonus []string `json:"-"` + Uploader string `json:"uploader"` + PreTime string `json:"pre_time"` + Other []string `json:"-"` + RawCookie string `json:"-"` + Seeders int `json:"-"` + Leechers int `json:"-"` + AdditionalSizeCheckRequired bool `json:"-"` + AdditionalUploaderCheckRequired bool `json:"-"` + FilterID int `json:"-"` + Filter *Filter `json:"-"` + ActionStatus []ReleaseActionStatus `json:"action_status"` } func (r *Release) Raw(s string) rls.Release { diff --git a/internal/feed/rss_test.go b/internal/feed/rss_test.go index a8bdc60..0c45257 100644 --- a/internal/feed/rss_test.go +++ b/internal/feed/rss_test.go @@ -73,7 +73,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", AnnounceType: domain.AnnounceTypeNew, 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)}, + want: &domain.Release{ID: 0, FilterStatus: "PENDING", Rejections: []string{}, Indexer: domain.IndexerMinimal{0, "Mock Feed", "mock-feed", "Mock Indexer"}, FilterName: "", Protocol: "torrent", Implementation: "RSS", AnnounceType: domain.AnnounceTypeNew, 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, AdditionalUploaderCheckRequired: false, FilterID: 0, Filter: (*domain.Filter)(nil), ActionStatus: []domain.ReleaseActionStatus(nil)}, }, { name: "with_baseurl", @@ -107,7 +107,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", AnnounceType: domain.AnnounceTypeNew, 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)}, + want: &domain.Release{ID: 0, FilterStatus: "PENDING", Rejections: []string{}, Indexer: domain.IndexerMinimal{0, "Mock Feed", "mock-feed", "Mock Indexer"}, FilterName: "", Protocol: "torrent", Implementation: "RSS", AnnounceType: domain.AnnounceTypeNew, 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, AdditionalUploaderCheckRequired: false, FilterID: 0, Filter: (*domain.Filter)(nil), ActionStatus: []domain.ReleaseActionStatus(nil)}, }, { name: "time_parse", @@ -142,7 +142,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", AnnounceType: domain.AnnounceTypeNew, 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)}, + want: &domain.Release{ID: 0, FilterStatus: "PENDING", Rejections: []string{}, Indexer: domain.IndexerMinimal{0, "Mock Feed", "mock-feed", "Mock Indexer"}, FilterName: "", Protocol: "torrent", Implementation: "RSS", AnnounceType: domain.AnnounceTypeNew, 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, AdditionalUploaderCheckRequired: false, FilterID: 0, Filter: (*domain.Filter)(nil), ActionStatus: []domain.ReleaseActionStatus(nil)}, }, { name: "time_parse", diff --git a/internal/filter/service.go b/internal/filter/service.go index 300d7b5..c144045 100644 --- a/internal/filter/service.go +++ b/internal/filter/service.go @@ -42,6 +42,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) + AdditionalUploaderCheck(ctx context.Context, f *domain.Filter, release *domain.Release) (bool, error) CheckSmartEpisodeCanDownload(ctx context.Context, params *domain.SmartEpisodeParams) (bool, error) GetDownloadsByFilterId(ctx context.Context, filterID int) (*domain.FilterDownloads, error) } @@ -429,8 +430,7 @@ func (s *service) CheckFilter(ctx context.Context, f *domain.Filter, release *do l.Debug().Msgf("found and matched filter: %s", f.Name) // If size constraints are set in a filter and the indexer did not - // announce the size, we need to do an additional out of band size - // check. + // announce the size, we need to do an additional out of band size check. if release.AdditionalSizeCheckRequired { l.Debug().Msgf("(%s) additional size check required", f.Name) @@ -446,6 +446,22 @@ func (s *service) CheckFilter(ctx context.Context, f *domain.Filter, release *do } } + // check uploader if the indexer supports check via api + if release.AdditionalUploaderCheckRequired { + l.Debug().Msgf("(%s) additional uploader check required", f.Name) + + ok, err := s.AdditionalUploaderCheck(ctx, f, release) + if err != nil { + l.Error().Err(err).Msgf("(%s) additional uploader check error", f.Name) + return false, err + } + + if !ok { + l.Trace().Msgf("(%s) additional uploader check not matching what filter wanted", f.Name) + return false, nil + } + } + // run external filters if f.External != nil { externalOk, err := s.RunExternalFilters(ctx, f, f.External, release) @@ -467,8 +483,8 @@ func (s *service) CheckFilter(ctx context.Context, f *domain.Filter, release *do return false, nil } -// AdditionalSizeCheck performs additional out of band checks to determine the -// size of a torrent. Some indexers do not announce torrent size, so it is +// AdditionalSizeCheck performs additional out-of-band checks to determine the +// values of a torrent. Some indexers do not announce torrent size, so it is // necessary to determine the size of the torrent in some other way. Some // indexers have an API implemented to fetch this data. For those which don't, // it is necessary to download the torrent file and parse it to make the size @@ -484,12 +500,12 @@ func (s *service) AdditionalSizeCheck(ctx context.Context, f *domain.Filter, rel // do additional size check against indexer api or torrent for size l := s.log.With().Str("method", "AdditionalSizeCheck").Logger() - l.Debug().Msgf("(%s) additional size check required", f.Name) + l.Debug().Msgf("(%s) additional api size check required", f.Name) switch release.Indexer.Identifier { - case "ptp", "btn", "ggn", "redacted", "ops", "mock": - if release.Size == 0 { - l.Trace().Msgf("(%s) preparing to check via api", f.Name) + case "btn", "ggn", "redacted", "ops", "mock": + if (release.Size == 0 && release.AdditionalSizeCheckRequired) || (release.Uploader == "" && release.AdditionalUploaderCheckRequired) { + l.Trace().Msgf("(%s) preparing to check size via api", f.Name) torrentInfo, err := s.apiService.GetTorrentByID(ctx, release.Indexer.Identifier, release.TorrentID) if err != nil || torrentInfo == nil { @@ -499,16 +515,25 @@ func (s *service) AdditionalSizeCheck(ctx context.Context, f *domain.Filter, rel l.Debug().Msgf("(%s) got torrent info from api: %+v", f.Name, torrentInfo) - release.Size = torrentInfo.ReleaseSizeBytes() + torrentSize := torrentInfo.ReleaseSizeBytes() + if release.Size == 0 && torrentSize > 0 { + release.Size = torrentSize + } + + if release.Uploader == "" { + release.Uploader = torrentInfo.Uploader + } } default: - l.Trace().Msgf("(%s) preparing to download torrent metafile", f.Name) + if release.Size == 0 && release.AdditionalSizeCheckRequired { + l.Trace().Msgf("(%s) preparing to download torrent metafile", f.Name) - // if indexer doesn't have api, download torrent and add to tmpPath - if err := s.downloadSvc.DownloadRelease(ctx, release); err != nil { - l.Error().Err(err).Msgf("(%s) could not download torrent file with id: '%s' from: %s", f.Name, release.TorrentID, release.Indexer.Identifier) - return false, errors.Wrap(err, "could not download torrent file for release: %s", release.TorrentName) + // if indexer doesn't have api, download torrent and add to tmpPath + if err := s.downloadSvc.DownloadRelease(ctx, release); err != nil { + l.Error().Err(err).Msgf("(%s) could not download torrent file with id: '%s' from: %s", f.Name, release.TorrentID, release.Indexer.Identifier) + return false, errors.Wrap(err, "could not download torrent file for release: %s", release.TorrentName) + } } } @@ -518,6 +543,9 @@ func (s *service) AdditionalSizeCheck(ctx context.Context, f *domain.Filter, rel return false, err } + // reset AdditionalSizeCheckRequired to not re-trigger check + release.AdditionalSizeCheckRequired = false + if !sizeOk { l.Debug().Msgf("(%s) filter did not match after additional size check, trying next", f.Name) return false, err @@ -526,6 +554,82 @@ func (s *service) AdditionalSizeCheck(ctx context.Context, f *domain.Filter, rel return true, nil } +func (s *service) AdditionalUploaderCheck(ctx context.Context, f *domain.Filter, release *domain.Release) (bool, error) { + var err error + defer func() { + // try recover panic if anything went wrong with API or size checks + errors.RecoverPanic(recover(), &err) + }() + + // do additional check against indexer api + l := s.log.With().Str("method", "AdditionalUploaderCheck").Logger() + + // if uploader was fetched before during size check we check it and return early + if release.Uploader != "" { + uploaderOk, err := f.CheckUploader(release.Uploader) + if err != nil { + l.Error().Err(err).Msgf("(%s) error comparing release and uploaders", f.Name) + return false, err + } + + // reset AdditionalUploaderCheckRequired to not re-trigger check + release.AdditionalUploaderCheckRequired = false + + if !uploaderOk { + l.Debug().Msgf("(%s) filter did not match after additional uploaders check, trying next", f.Name) + return false, err + } + + return true, nil + } + + l.Debug().Msgf("(%s) additional api size check required", f.Name) + + switch release.Indexer.Identifier { + case "redacted", "ops", "mock": + l.Trace().Msgf("(%s) preparing to check via api", f.Name) + + torrentInfo, err := s.apiService.GetTorrentByID(ctx, release.Indexer.Identifier, release.TorrentID) + if err != nil || torrentInfo == nil { + l.Error().Err(err).Msgf("(%s) could not get torrent info from api: '%s' from: %s", f.Name, release.TorrentID, release.Indexer.Identifier) + return false, err + } + + l.Debug().Msgf("(%s) got torrent info from api: %+v", f.Name, torrentInfo) + + torrentSize := torrentInfo.ReleaseSizeBytes() + if release.Size == 0 && torrentSize > 0 { + release.Size = torrentSize + + // reset AdditionalSizeCheckRequired to not re-trigger check + release.AdditionalSizeCheckRequired = false + } + + if release.Uploader == "" { + release.Uploader = torrentInfo.Uploader + } + + default: + return false, errors.New("additional uploader check not supported for this indexer: %s", release.Indexer.Identifier) + } + + uploaderOk, err := f.CheckUploader(release.Uploader) + if err != nil { + l.Error().Err(err).Msgf("(%s) error comparing release and uploaders", f.Name) + return false, err + } + + // reset AdditionalUploaderCheckRequired to not re-trigger check + release.AdditionalUploaderCheckRequired = false + + if !uploaderOk { + l.Debug().Msgf("(%s) filter did not match after additional uploaders check, trying next", f.Name) + return false, err + } + + return true, nil +} + func (s *service) CheckSmartEpisodeCanDownload(ctx context.Context, params *domain.SmartEpisodeParams) (bool, error) { return s.releaseRepo.CheckSmartEpisodeCanDownload(ctx, params) } diff --git a/pkg/red/red.go b/pkg/red/red.go index d97d163..36e1274 100644 --- a/pkg/red/red.go +++ b/pkg/red/red.go @@ -234,6 +234,7 @@ func (c *Client) GetTorrentByID(ctx context.Context, torrentID string) (*domain. Id: strconv.Itoa(response.Response.Torrent.Id), InfoHash: response.Response.Torrent.InfoHash, Size: strconv.Itoa(response.Response.Torrent.Size), + Uploader: response.Response.Torrent.Username, }, nil } diff --git a/pkg/red/red_test.go b/pkg/red/red_test.go index b72833c..e084503 100644 --- a/pkg/red/red_test.go +++ b/pkg/red/red_test.go @@ -75,6 +75,7 @@ func TestREDClient_GetTorrentByID(t *testing.T) { Id: "29991962", InfoHash: "B2BABD3A361EAFC6C4E9142C422DF7DDF5D7E163", Size: "527749302", + Uploader: "Uploader", }, wantErr: "", }, diff --git a/pkg/red/testdata/get_torrent_by_id.json b/pkg/red/testdata/get_torrent_by_id.json index f6af2db..b6155cd 100644 --- a/pkg/red/testdata/get_torrent_by_id.json +++ b/pkg/red/testdata/get_torrent_by_id.json @@ -71,7 +71,7 @@ "fileList": "00-logistics-fear_not-cd-flac-2012.jpg{{{1233205}}}|||00-logistics-fear_not-cd-flac-2012.m3u{{{538}}}|||00-logistics-fear_not-cd-flac-2012.nfo{{{1607}}}|||00-logistics-fear_not-cd-flac-2012.sfv{{{688}}}|||01-logistics-fear_not.flac{{{38139451}}}|||02-logistics-timelapse.flac{{{39346037}}}|||03-logistics-2999_(wherever_you_go).flac{{{41491133}}}|||04-logistics-try_again.flac{{{32151567}}}|||05-logistics-we_are_one.flac{{{40778041}}}|||06-logistics-crystal_skies_(feat_nightshade_and_sarah_callander).flac{{{34544405}}}|||07-logistics-feels_so_good.flac{{{41363732}}}|||08-logistics-running_late.flac{{{16679269}}}|||09-logistics-early_again.flac{{{35373278}}}|||10-logistics-believe_in_me.flac{{{39495420}}}|||11-logistics-letting_go.flac{{{30846730}}}|||12-logistics-sendai_song.flac{{{35021141}}}|||13-logistics-over_and_out.flac{{{44621200}}}|||14-logistics-destination_unknown.flac{{{13189493}}}|||15-logistics-watching_the_world_go_by_(feat_alice_smith).flac{{{43472367}}}", "filePath": "Logistics-Fear_Not-CD-FLAC-2012-TaBoo", "userId": 567, - "username": null + "username": "Uploader" } } } \ No newline at end of file