Files
zurg/internal/config/v1.go
2024-06-05 09:20:40 +02:00

384 lines
10 KiB
Go

package config
import (
"fmt"
"regexp"
"sort"
"strconv"
"strings"
"github.com/debridmediamanager/zurg/pkg/logutil"
"github.com/debridmediamanager/zurg/pkg/utils"
"gopkg.in/yaml.v3"
)
const (
ALL_TORRENTS = "__all__"
UNPLAYABLE_TORRENTS = "__unplayable__"
DUMPED_TORRENTS = "__dump__"
DOWNLOADS = "__downloads__"
)
func loadV1Config(content []byte, log *logutil.Logger) (*ZurgConfigV1, error) {
var configV1 ZurgConfigV1
if err := yaml.Unmarshal(content, &configV1); err != nil {
return nil, err
}
// don't log token and password
bufToken := configV1.Token
configV1.Token = strings.Repeat("*", len(bufToken)-4) + bufToken[len(bufToken)-4:]
bufPassword := configV1.Password
configV1.Password = strings.Repeat("*", len(bufPassword))
log.Debugf("Config dump: %+v", configV1)
configV1.Token = bufToken
configV1.Password = bufPassword
configV1.log = log
return &configV1, nil
}
func (z *ZurgConfigV1) GetVersion() string {
return "v1"
}
func (z *ZurgConfigV1) GetDirectories() []string {
rootDirectories := make([]string, len(z.Directories)+3)
i := 0
for directory := range z.Directories {
rootDirectories[i] = directory
i++
}
rootDirectories[i] = ALL_TORRENTS
rootDirectories[i+1] = UNPLAYABLE_TORRENTS
rootDirectories[i+2] = DUMPED_TORRENTS
return rootDirectories
}
func (z *ZurgConfigV1) GetDirectoryConfig(directory string) DirectoryV1 {
if dirCfg, ok := z.Directories[directory]; ok {
return *dirCfg
}
return DirectoryV1{}
}
func (z *ZurgConfigV1) GetGroupMap() map[string][]string {
var groupMap = make(map[string][]string)
var groupOrderMap = make(map[string]int) // To store GroupOrder for each directory
// Populate the groupMap and groupOrderMap
for directory, val := range z.Directories {
groupMap[val.Group] = append(groupMap[val.Group], directory)
groupOrderMap[directory] = val.GroupOrder
}
// Sort the slice based on GroupOrder and then directory name for deterministic order
for group, dirs := range groupMap {
sort.Slice(dirs, func(i, j int) bool {
if groupOrderMap[dirs[i]] == groupOrderMap[dirs[j]] {
return dirs[i] < dirs[j] // Use directory name as secondary sort criterion
}
return groupOrderMap[dirs[i]] < groupOrderMap[dirs[j]]
})
groupMap[group] = dirs
}
// Return a deep copy of the map
result := make(map[string][]string)
for k, v := range groupMap {
temp := make([]string, len(v))
copy(temp, v)
result[k] = temp
// result[k] = append(result[k], UNPLAYABLE_TORRENTS)
}
result[ALL_TORRENTS] = []string{ALL_TORRENTS} // Add special group for all torrents
return result
}
func (z *ZurgConfigV1) MeetsConditions(directory, torrentName string, torrentSize int64, torrentIDs, fileNames []string, fileSizes []int64) bool {
if directory == ALL_TORRENTS {
return true
}
if _, ok := z.Directories[directory]; !ok {
return false
}
for _, filter := range z.Directories[directory].Filters {
if z.matchFilter(torrentName, torrentSize, torrentIDs, fileNames, fileSizes, filter) {
return true
}
}
return false
}
func (z *ZurgConfigV1) matchFilter(torrentName string, torrentSize int64, torrentIDs, fileNames []string, fileSizes []int64, filter *FilterConditionsV1) bool {
if filter.ID != "" {
for _, torrentID := range torrentIDs {
if torrentID == filter.ID {
return true
}
}
}
if filter.RegexStr != "" {
regex, err := compilePattern(filter.RegexStr)
if err != nil {
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
}
if filter.Contains != "" && strings.Contains(strings.ToLower(torrentName), strings.ToLower(filter.Contains)) {
return true
}
if filter.NotContainsStrict != "" && !strings.Contains(torrentName, filter.NotContainsStrict) {
return true
}
if filter.NotContains != "" && !strings.Contains(strings.ToLower(torrentName), strings.ToLower(filter.NotContains)) {
return true
}
if filter.SizeGreaterThanOrEqual > 0 && torrentSize >= filter.SizeGreaterThanOrEqual {
return true
}
if filter.SizeLessThanOrEqual > 0 && torrentSize <= filter.SizeLessThanOrEqual {
return true
}
if len(filter.And) > 0 {
andResult := true
for _, andFilter := range filter.And {
andResult = andResult && z.matchFilter(torrentName, torrentSize, torrentIDs, fileNames, fileSizes, andFilter)
if !andResult {
return false
}
}
return true
}
if len(filter.Or) > 0 {
for _, orFilter := range filter.Or {
if z.matchFilter(torrentName, torrentSize, torrentIDs, fileNames, fileSizes, orFilter) {
return true
}
}
}
if filter.FileInsideRegexStr != "" {
regex, err := compilePattern(filter.FileInsideRegexStr)
if err != nil {
z.log.Errorf("Failed to compile any_file_inside_regex %s error: %v", filter.FileInsideRegexStr, err)
return false
}
for _, filename := range fileNames {
if regex.MatchString(filename) {
return true
}
}
}
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 {
if strings.Contains(filename, filter.FileInsideContainsStrict) {
return true
}
}
return false
}
if filter.FileInsideNotContainsStrict != "" {
for _, filename := range fileNames {
if !strings.Contains(filename, filter.FileInsideNotContainsStrict) {
return true
}
}
return false
}
if filter.HasEpisodes {
regexes := []*regexp.Regexp{
regexp.MustCompile(`(?i)s\d\d\d?.?e\d\d\d?`),
regexp.MustCompile(`(?i)seasons?\s?\d+`),
regexp.MustCompile(`(?i)episodes?\s?\d+`),
regexp.MustCompile(`(?i)[a-fA-F0-9]{8}`),
}
for _, regex := range regexes {
if regex.MatchString(torrentName) {
return true
}
for _, filename := range fileNames {
if regex.MatchString(filename) {
return true
}
}
}
//remove resolution from filenames
regex := regexp.MustCompile(`(?i)((720|1080|2160|480|360|240|144)[pi]|\d{3,4}x\d{3,4})`)
for i, filename := range fileNames {
fileNames[i] = regex.ReplaceAllString(filename, "")
}
// remove year from filenames
regex = regexp.MustCompile(`(19\d|20[0-2])\d`)
for i, filename := range fileNames {
fileNames[i] = regex.ReplaceAllString(filename, "")
}
// remove filenames with "extras" in name
regex = regexp.MustCompile(`(?i)extras`)
for i, filename := range fileNames {
if regex.MatchString(filename) {
fileNames[i] = ""
}
}
return checkArithmeticSequenceInFilenames(fileNames)
}
if filter.IsMusic {
musicExts := []string{".m3u", ".mp3", ".flac"}
for _, filename := range fileNames {
for _, ext := range musicExts {
if strings.HasSuffix(strings.ToLower(filename), ext) {
return true
}
}
}
return false
}
if filter.FileInsideSizeGreaterThanOrEqual > 0 {
for _, fileSize := range fileSizes {
if fileSize >= filter.FileInsideSizeGreaterThanOrEqual {
return true
}
}
}
if filter.FileInsideSizeLessThanOrEqual > 0 {
for _, fileSize := range fileSizes {
if fileSize <= filter.FileInsideSizeLessThanOrEqual {
return true
}
}
}
return false
}
func compilePattern(pattern string) (*regexp.Regexp, error) {
flags := map[rune]string{
'i': "(?i)",
'm': "(?m)",
's': "(?s)",
'x': "(?x)",
}
lastSlash := strings.LastIndex(pattern, "/")
secondLastSlash := strings.LastIndex(pattern[:lastSlash], "/")
// Extract the core pattern
corePattern := pattern[secondLastSlash+1 : lastSlash]
// Extract and process flags
flagSection := pattern[lastSlash+1:]
flagString := ""
processedFlags := make(map[rune]bool)
for _, flag := range flagSection {
if replacement, ok := flags[flag]; ok && !processedFlags[flag] {
flagString += replacement
processedFlags[flag] = true
}
}
// Combine the processed flags with the core pattern
finalPattern := flagString + corePattern
// Validate pattern
if finalPattern == "" || finalPattern == flagString {
return nil, fmt.Errorf("invalid regex pattern: %s", pattern)
}
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.IsPlayable(file) {
continue
}
matches := r.FindAllStringIndex(file, -1)
for _, match := range matches {
numSet := make(map[int]struct{})
for _, file := range files {
if !utils.IsPlayable(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
}