diff --git a/internal/config/v1.go b/internal/config/v1.go index cad43cc..15ecc63 100644 --- a/internal/config/v1.go +++ b/internal/config/v1.go @@ -4,8 +4,10 @@ import ( "fmt" "regexp" "sort" + "strconv" "strings" + "github.com/debridmediamanager.com/zurg/pkg/utils" "go.uber.org/zap" "gopkg.in/yaml.v3" ) @@ -88,13 +90,23 @@ func (z *ZurgConfigV1) matchFilter(torrentName string, torrentIDs, fileNames []s if filter.RegexStr != "" { regex, err := compilePattern(filter.RegexStr) if err != nil { - z.log.Errorf("Failed to compile regex: %v", err) + z.log.Errorf("Failed to compile regex %s error: %v", filter.RegexStr, err) return false } if regex.MatchString(torrentName) { return true } } + if filter.NotRegexStr != "" { + regex, err := compilePattern(filter.NotRegexStr) + if err != nil { + z.log.Errorf("Failed to compile not_regex %s error: %v", filter.NotRegexStr, err) + return false + } + if !regex.MatchString(torrentName) { + return true + } + } if filter.ContainsStrict != "" && strings.Contains(torrentName, filter.ContainsStrict) { return true } @@ -127,7 +139,7 @@ func (z *ZurgConfigV1) matchFilter(torrentName string, torrentIDs, fileNames []s if filter.FileInsideRegexStr != "" { regex, err := compilePattern(filter.FileInsideRegexStr) if err != nil { - z.log.Errorf("Failed to compile regex: %v", err) + z.log.Errorf("Failed to compile any_file_inside_regex %s error: %v", filter.FileInsideRegexStr, err) return false } for _, filename := range fileNames { @@ -136,12 +148,34 @@ func (z *ZurgConfigV1) matchFilter(torrentName string, torrentIDs, fileNames []s } } } + if filter.FileInsideNotRegexStr != "" { + regex, err := compilePattern(filter.FileInsideNotRegexStr) + if err != nil { + z.log.Errorf("Failed to compile any_file_inside_not_regex %s error: %v", filter.FileInsideNotRegexStr, err) + return false + } + for _, filename := range fileNames { + if !regex.MatchString(filename) { + return true + } + } + return false + } if filter.FileInsideContains != "" { for _, filename := range fileNames { if strings.Contains(strings.ToLower(filename), strings.ToLower(filter.FileInsideContains)) { return true } } + return false + } + if filter.FileInsideNotContains != "" { + for _, filename := range fileNames { + if !strings.Contains(strings.ToLower(filename), strings.ToLower(filter.FileInsideNotContains)) { + return true + } + } + return false } if filter.FileInsideContainsStrict != "" { for _, filename := range fileNames { @@ -149,6 +183,24 @@ func (z *ZurgConfigV1) matchFilter(torrentName string, torrentIDs, fileNames []s return true } } + return false + } + if filter.FileInsideNotContainsStrict != "" { + for _, filename := range fileNames { + if !strings.Contains(filename, filter.FileInsideNotContainsStrict) { + return true + } + } + return false + } + if filter.HasEpisodes { + regex := regexp.MustCompile(`(?i)s\d\de\d\d`) + for _, filename := range fileNames { + if regex.MatchString(filename) { + return true + } + } + return checkArithmeticSequenceInFilenames(fileNames) } return false } @@ -188,3 +240,54 @@ func compilePattern(pattern string) (*regexp.Regexp, error) { return regexp.Compile(finalPattern) } + +func hasIncreasingSequence(arr []int) bool { + if len(arr) < 3 { + return false + } + for i := 0; i < len(arr)-2; i++ { + if arr[i] < arr[i+1] && arr[i+1] < arr[i+2] { + return true + } + } + return false +} + +func checkArithmeticSequenceInFilenames(files []string) bool { + if len(files) < 3 { + return false + } + r := regexp.MustCompile(`\d+`) + for _, file := range files { + if !utils.IsStreamable(file) { + continue + } + matches := r.FindAllStringIndex(file, -1) + for _, match := range matches { + numSet := make(map[int]struct{}) + for _, file := range files { + if !utils.IsStreamable(file) { + continue + } + if match[0] >= 0 && match[1] <= len(file) { + num, err := strconv.Atoi(file[match[0]:match[1]]) + if err == nil { + numSet[num] = struct{}{} + } + } else { + // out of bounds, ignore + continue + } + } + numList := make([]int, 0, len(numSet)) + for num := range numSet { + numList = append(numList, num) + } + sort.Ints(numList) + if hasIncreasingSequence(numList) { + return true + } + } + } + return false +} diff --git a/internal/config/v1types.go b/internal/config/v1types.go index 0300656..4a373f1 100644 --- a/internal/config/v1types.go +++ b/internal/config/v1types.go @@ -18,13 +18,19 @@ type FilterConditionsV1 struct { RegexStr string `yaml:"regex"` Contains string `yaml:"contains"` ContainsStrict string `yaml:"contains_strict"` + NotRegexStr string `yaml:"not_regex"` NotContains string `yaml:"not_contains"` NotContainsStrict string `yaml:"not_contains_strict"` And []*FilterConditionsV1 `yaml:"and"` Or []*FilterConditionsV1 `yaml:"or"` - FileInsideRegexStr string `yaml:"any_file_inside_regex"` - FileInsideContains string `yaml:"any_file_inside_contains"` - FileInsideContainsStrict string `yaml:"any_file_inside_contains_strict"` + FileInsideRegexStr string `yaml:"any_file_inside_regex"` + FileInsideContains string `yaml:"any_file_inside_contains"` + FileInsideContainsStrict string `yaml:"any_file_inside_contains_strict"` + FileInsideNotRegexStr string `yaml:"any_file_inside_not_regex"` + FileInsideNotContains string `yaml:"any_file_inside_not_contains"` + FileInsideNotContainsStrict string `yaml:"any_file_inside_not_contains_strict"` + + HasEpisodes bool `yaml:"has_episodes"` } diff --git a/internal/torrent/manager.go b/internal/torrent/manager.go index 28a9220..77f45af 100644 --- a/internal/torrent/manager.go +++ b/internal/torrent/manager.go @@ -15,6 +15,7 @@ import ( "github.com/debridmediamanager.com/zurg/internal/config" "github.com/debridmediamanager.com/zurg/pkg/logutil" "github.com/debridmediamanager.com/zurg/pkg/realdebrid" + "github.com/debridmediamanager.com/zurg/pkg/utils" cmap "github.com/orcaman/concurrent-map/v2" "github.com/panjf2000/ants/v2" "go.uber.org/zap" @@ -96,9 +97,7 @@ func NewTorrentManager(cfg config.ConfigInterface, api *realdebrid.RealDebrid, p } } - anotherCt := 0 allTorrents.IterCb(func(accessKey string, torrent *Torrent) { - anotherCt++ // get IDs var torrentIDs []string for _, instance := range torrent.Instances { @@ -293,11 +292,7 @@ func (t *TorrentManager) startRefreshJob() { } // get filenames - var filenames []string - torrent.SelectedFiles.IterCb(func(_ string, file *File) { - filenames = append(filenames, file.Path) - }) - + filenames := torrent.SelectedFiles.Keys() // Map torrents to directories switch t.cfg.GetVersion() { case "v1": @@ -593,7 +588,7 @@ func (t *TorrentManager) Repair(accessKey string) { var links []string streamableCount := 0 torrent.SelectedFiles.IterCb(func(_ string, file *File) { - if isStreamable(file.Path) { + if utils.IsStreamable(file.Path) { streamableCount++ } fileCopy := &File{ diff --git a/internal/torrent/util.go b/internal/torrent/util.go index 2e3fe79..0bcd2d4 100644 --- a/internal/torrent/util.go +++ b/internal/torrent/util.go @@ -3,7 +3,6 @@ package torrent import ( "fmt" "os" - "strings" ) func getFileIDs(files []File) []string { @@ -16,14 +15,6 @@ func getFileIDs(files []File) []string { return fileIDs } -func isStreamable(filePath string) bool { - return strings.HasSuffix(filePath, ".mkv") || - strings.HasSuffix(filePath, ".mp4") || - strings.HasSuffix(filePath, ".avi") || - strings.HasSuffix(filePath, ".wmv") || - strings.HasSuffix(filePath, ".m4v") -} - func ensureDir(dirName string) error { if _, err := os.Stat(dirName); os.IsNotExist(err) { err := os.Mkdir(dirName, 0755) diff --git a/pkg/utils/streamable.go b/pkg/utils/streamable.go new file mode 100644 index 0000000..10df276 --- /dev/null +++ b/pkg/utils/streamable.go @@ -0,0 +1,11 @@ +package utils + +import "strings" + +func IsStreamable(filePath string) bool { + return strings.HasSuffix(filePath, ".mkv") || + strings.HasSuffix(filePath, ".mp4") || + strings.HasSuffix(filePath, ".avi") || + strings.HasSuffix(filePath, ".wmv") || + strings.HasSuffix(filePath, ".m4v") +}