diff --git a/anilist.go b/anilist.go index 4b89cf2..c125d13 100644 --- a/anilist.go +++ b/anilist.go @@ -13,7 +13,7 @@ type Pair[A, B any] struct { Second B } -func GetCurrentlyWatchingAnimesByAnimeID() (map[anilist.MediaID]*anilist.MediaList, error) { +func GetAnimesToDownloadByAnimeID() (map[anilist.MediaID]*anilist.MediaList, error) { watchingAnimesChannel, err := GetCurrentlyWatchingAnimes() if err != nil { return nil, err diff --git a/go.mod b/go.mod index 0b9901c..4a73f9c 100644 --- a/go.mod +++ b/go.mod @@ -3,15 +3,15 @@ module git.milar.in/nyaanime/downloader go 1.18 require ( - git.milar.in/milarin/adverr v0.2.1 + git.milar.in/milarin/adverr v1.0.0 git.milar.in/milarin/anilist v1.5.0 git.milar.in/milarin/channel v0.0.7 git.milar.in/milarin/envvars v1.0.3 - git.milar.in/milarin/slices v0.0.0-20220818114116-f7ab541d0a5b - git.milar.in/nyaanime/model v0.0.0-20220815143950-c3d8a5af20c3 + git.milar.in/milarin/gmath v0.0.2 + git.milar.in/milarin/slices v0.0.2 + git.milar.in/nyaanime/model v0.0.0-20220821124037-0a28c6b41556 git.milar.in/nyaanime/parsers v0.0.0-20220815144327-52de61265e27 github.com/PuerkitoBio/goquery v1.8.0 - golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e ) require ( diff --git a/go.sum b/go.sum index 1c8881d..f10033b 100644 --- a/go.sum +++ b/go.sum @@ -1,23 +1,23 @@ -git.milar.in/milarin/adverr v0.2.1 h1:eyXFGC+Ui/kcNt2+NqP3HiAplwxzqeNr9DfitsUb3c4= -git.milar.in/milarin/adverr v0.2.1/go.mod h1:wwfglcey4R3vqjNL/d8mbnvFJGzETRXzAEolIHZY32w= +git.milar.in/milarin/adverr v1.0.0 h1:9povP074PjpoZlNuiKPa9w6fDA8a4RSI5hEDo5T9Tqo= +git.milar.in/milarin/adverr v1.0.0/go.mod h1:wwfglcey4R3vqjNL/d8mbnvFJGzETRXzAEolIHZY32w= git.milar.in/milarin/anilist v1.5.0 h1:fSiAXY/topNk4ISEp2QtcG9HHKLJfMc8w05iqc+Paf0= git.milar.in/milarin/anilist v1.5.0/go.mod h1:8PTHXFMA45JpfRFIpcdrKwDHue8fbT/wwV1BuHFn6c0= git.milar.in/milarin/channel v0.0.7 h1:cVKtwgH/EE7U+XTHcoFCClJ4LR349KanzjX9xKwRcNg= git.milar.in/milarin/channel v0.0.7/go.mod h1:We83LTI8S7u7II3pD+A2ChCDWJfCkcBUCUqii9HjTtM= git.milar.in/milarin/envvars v1.0.3 h1:go6pYExUzPx+aLvJ5BKNkaANkBNmcrlRJ8TtWsk4uWY= git.milar.in/milarin/envvars v1.0.3/go.mod h1:rLh/HN6S254h6m2lklnImcpsy4kHFxaOjM6+Nv9GHKI= -git.milar.in/milarin/slices v0.0.0-20220818114116-f7ab541d0a5b h1:886jJlrbGpjKijCvtK9Nzwi4JZhWFFCTRWBc4yiM+TY= -git.milar.in/milarin/slices v0.0.0-20220818114116-f7ab541d0a5b/go.mod h1:XRNfE99aNKeaPOY1phjOlpIQqeGCW1LOqqh8UHS+vAk= -git.milar.in/nyaanime/model v0.0.0-20220815143950-c3d8a5af20c3 h1:GyAhYs3Nl8CuzfOeLKmWwRe/Z+pEEtnWty39HfGz6iE= -git.milar.in/nyaanime/model v0.0.0-20220815143950-c3d8a5af20c3/go.mod h1:OzhQgj0b/Hf9fg8VXYxYt8ONQOvHm8xC44TmS9kQ150= +git.milar.in/milarin/gmath v0.0.2 h1:avz+75f8XqAYA1wEB6kis0R5xvRuepBKTqBuJBjh6Yw= +git.milar.in/milarin/gmath v0.0.2/go.mod h1:HDLftG5RLpiNGKiIWh+O2G1PYkNzyLDADO8Cd/1abiE= +git.milar.in/milarin/slices v0.0.2 h1:j92MuP0HWKSaHJMq/FRxDtSDIGiOTvw+KogUTwuulr0= +git.milar.in/milarin/slices v0.0.2/go.mod h1:XRNfE99aNKeaPOY1phjOlpIQqeGCW1LOqqh8UHS+vAk= +git.milar.in/nyaanime/model v0.0.0-20220821124037-0a28c6b41556 h1:RplYz4+CMK9ByI3ELusBltWFRlDs6VMOGk5EyENnLi0= +git.milar.in/nyaanime/model v0.0.0-20220821124037-0a28c6b41556/go.mod h1:OzhQgj0b/Hf9fg8VXYxYt8ONQOvHm8xC44TmS9kQ150= git.milar.in/nyaanime/parsers v0.0.0-20220815144327-52de61265e27 h1:0+5j9MMJQS8+Luss19hD6hvNFxcBDRal2XwSUTyq7WU= git.milar.in/nyaanime/parsers v0.0.0-20220815144327-52de61265e27/go.mod h1:qm6fIFBFs90uz7IJ8RKgDir0K8Fa8isixGLgrtC6kgU= github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U= github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI= github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c= github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= -golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA= -golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220812174116-3211cb980234 h1:RDqmgfe7SvlMWoqC3xwQ2blLO3fcWcxMa3eBLRdRW7E= golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= diff --git a/main.go b/main.go index 2b4a508..7f4ae69 100644 --- a/main.go +++ b/main.go @@ -28,8 +28,8 @@ var ( MaxResolution = envvars.Object("MAX_RESOLUTION", model.Resolution4K, model.ParseResolution) MinResolution = envvars.Object("MIN_RESOLUTION", model.ResolutionHD, model.ParseResolution) - EssentialLanguages = envvars.StringSlice("ESSENTIAL_LANGUAGES", ",") - EssentialSubtitles = envvars.StringSlice("ESSENTIAL_SUBTITLES", ",") + EssentialLanguages = envvars.StringSlice("ESSENTIAL_LANGUAGES", "|") + EssentialSubtitles = envvars.StringSlice("ESSENTIAL_SUBTITLES", "|") MaxSeeders = envvars.Int("MAX_SEEDERS", math.MaxInt) MinSeeders = envvars.Int("MIN_SEEDERS", 0) @@ -44,8 +44,9 @@ var ( // preferred torrent properties - PreferredLanguages = envvars.StringSlice("PREFERRED_LANGUAGES", ",") - PreferredSubtitles = envvars.StringSlice("PREFERRED_SUBTITLES", ",") + PreferredLanguages = ParsePreferredStringProps(envvars.StringSlice("PREFERRED_LANGUAGES", "|")) + PreferredSubtitles = ParsePreferredStringProps(envvars.StringSlice("PREFERRED_SUBTITLES", "|")) + PreferredResolutions = ParsePreferredProps(envvars.StringSlice("PREFERRED_RESOLUTIONS", "|"), model.ParseResolution) /* TODO @@ -56,6 +57,10 @@ var ( ) func main() { + fmt.Println("language priorites:", PreferredLanguages) + fmt.Println("subtitle priorites:", PreferredSubtitles) + fmt.Println("resolution priorites:", PreferredResolutions) + if len(AnimeStatus) == 0 { AnimeStatus = []anilist.MediaListStatus{ anilist.MediaListStatusCurrent, @@ -87,20 +92,36 @@ func checkTorrents() { return } - _, err = GetCurrentlyWatchingAnimesByAnimeID() + /*animes, err = GetAnimesToDownloadByAnimeID() if err != nil { fmt.Println(adverr.Wrap("retrieving anime list failed", err)) return - } + }*/ - parsedTorrentsByAnimeEp := FilterEssentialTorrents(ParseTorrentsByAnimeEp(torrents)) + parsedTorrents := ParseTorrentsByAnimeEpSortedByProperties(torrents) - for _, parsedTorrents := range parsedTorrentsByAnimeEp { - for _, parsedTorrent := range parsedTorrents { - fmt.Println(parsedTorrent, "|", parsedTorrent.Torrent.StringWithoutTitle()) + for animeEp, torrentPriorities := range parsedTorrents { + fmt.Printf("\nanime: %s | episode: %d | torrents found: %d\n", animeEp.Anime, animeEp.Episode, len(torrentPriorities)) + for _, torrentPriority := range torrentPriorities { + PrintTorrentPriority(torrentPriority) } } + // TODO store preferred properties of downloaded torrents in db + duration := time.Since(start) - fmt.Printf("check took %s. sleeping for %s\n", duration, PollRate-duration) + fmt.Printf("\ncheck took %s. sleeping for %s\n", duration.Truncate(time.Millisecond), (PollRate - duration).Truncate(time.Millisecond)) +} + +func ParseTorrentsByAnimeEpSortedByProperties(torrents []model.Torrent) map[model.AnimeEpisode][]*TorrentPriority { + // parse torrents + parsedTorrentsByAnimeEp := ParseTorrentsByAnimeEp(torrents) + + // filter out torrents without essential properties + filteredTorrentsByAnimeEp := FilterEssentialTorrents(parsedTorrentsByAnimeEp) + + // sort torrents by preferred properties + sortedTorrentsByAnimeEp := SortTorrentsByPreferredProperties(filteredTorrentsByAnimeEp) + + return sortedTorrentsByAnimeEp } diff --git a/parse_preferred_props.go b/parse_preferred_props.go new file mode 100644 index 0000000..6b1f4c9 --- /dev/null +++ b/parse_preferred_props.go @@ -0,0 +1,53 @@ +package main + +import ( + "strings" + + "git.milar.in/milarin/gmath" + "git.milar.in/milarin/slices" +) + +// ParsePreferredProps parses properties and its corresponding priority. +// priorities are distributed exponentially in reverse order. +// +// That means the last entry will have priority 1, the second last 2, then 4, 8 and so on. +// +// Properties with name "_" will be ignored and function as a placeholder to increase the priority +// of the properties which comes before them. +// +// Properties separated by comma will have the same priorities. +// +// str usually is the return value of a call to strings.Split(str, "|") +// +// Examples: +// str = "a|b|c" -> c:1 b:2 a:4 +// str = "a|b|_|c" -> c:1 b:4 a:8 +// str = "a,b|c" -> c:1 b:4 a:4 +// str = "d|_|a,b|c" -> c:1 b:4 a:4 d:16 +// +// Additionally, properties can be converted to a generic type with the converter function +func ParsePreferredProps[T comparable](str []string, converter func(string) (T, error)) map[T]int { + props := map[T]int{} + + for i, subProps := range slices.Reverse(str) { + if subProps == "_" { + continue + } + + propPriority := gmath.Pow(2, i) + for _, subProp := range strings.Split(subProps, ",") { + subPropT, err := converter(subProp) + if err != nil { + continue + } + + props[subPropT] = propPriority + } + } + + return props +} + +func ParsePreferredStringProps(str []string) map[string]int { + return ParsePreferredProps(str, func(s string) (string, error) { return s, nil }) +} diff --git a/torrent_filter.go b/torrent_filter.go index e0f903d..b366542 100644 --- a/torrent_filter.go +++ b/torrent_filter.go @@ -1,8 +1,8 @@ package main import ( + "git.milar.in/milarin/slices" "git.milar.in/nyaanime/model" - "golang.org/x/exp/slices" ) func FilterEssentialTorrents(parsedTorrentsByAnimeEp map[model.AnimeEpisode][]*model.ParsedTorrent) map[model.AnimeEpisode][]*model.ParsedTorrent { diff --git a/torrent_priority.go b/torrent_priority.go new file mode 100644 index 0000000..cd87a79 --- /dev/null +++ b/torrent_priority.go @@ -0,0 +1,27 @@ +package main + +import ( + "fmt" + + "git.milar.in/nyaanime/model" +) + +type TorrentPriority struct { + ParsedTorrent *model.ParsedTorrent + Priority int + PreferredProperties map[string]int +} + +func NewTorrentPriority(torrent *model.ParsedTorrent) *TorrentPriority { + priority, preferredProperties := DeterminePriority(torrent) + + return &TorrentPriority{ + ParsedTorrent: torrent, + Priority: priority, + PreferredProperties: preferredProperties, + } +} + +func (tp TorrentPriority) String() string { + return fmt.Sprintf("%s | priority: %d", tp.ParsedTorrent.String(), tp.Priority) +} diff --git a/torrent_sort.go b/torrent_sort.go index 7068fca..ed19d0a 100644 --- a/torrent_sort.go +++ b/torrent_sort.go @@ -1,9 +1,45 @@ package main -import "git.milar.in/nyaanime/model" +import ( + "sort" -func SortTorrentsByPreferredProperties(torrents map[model.AnimeEpisode][]*model.ParsedTorrent) { - // TODO - // sort torrent slices by preferred properties - // store prefered properties in db somewhere + "git.milar.in/milarin/slices" + "git.milar.in/nyaanime/model" +) + +func SortTorrentsByPreferredProperties(torrents map[model.AnimeEpisode][]*model.ParsedTorrent) map[model.AnimeEpisode][]*TorrentPriority { + torrentsWithPrio := map[model.AnimeEpisode][]*TorrentPriority{} + + for animeEp, torrentList := range torrents { + torrentPrioList := slices.Map(torrentList, NewTorrentPriority) + sort.Slice(torrentPrioList, func(i, j int) bool { return torrentPrioList[i].Priority > torrentPrioList[j].Priority }) + torrentsWithPrio[animeEp] = torrentPrioList + } + + return torrentsWithPrio +} + +func DeterminePriority(torrent *model.ParsedTorrent) (priority int, preferredProperties map[string]int) { + preferredProperties = map[string]int{} + + for _, lang := range torrent.Languages { + if langPriority, ok := PreferredLanguages[lang]; ok { + priority += langPriority + preferredProperties["lang/"+lang] = langPriority + } + } + + for _, sub := range torrent.Subtitles { + if subPriority, ok := PreferredSubtitles[sub]; ok { + priority += subPriority + preferredProperties["sub/"+sub] = subPriority + } + } + + if prefRes, ok := PreferredResolutions[torrent.Resolution]; ok { + priority += prefRes + preferredProperties["res/"+torrent.Resolution.String()] = prefRes + } + + return } diff --git a/utils.go b/utils.go index ec852dc..eedb3d8 100644 --- a/utils.go +++ b/utils.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "strings" "git.milar.in/milarin/anilist" @@ -29,3 +30,30 @@ func ParseMediaListStatus(str string) (anilist.MediaListStatus, error) { return s, nil } + +func Map2Str[K comparable, T any](m map[K]T) string { + b := new(strings.Builder) + + for k, v := range m { + b.WriteString(fmt.Sprintf("%v:%v ", k, v)) + } + + str := b.String() + + return str[:len(str)-1] +} + +func PrintTorrentPriority(torrentPriority *TorrentPriority) { + fmt.Printf("id: %s | resolution: %d | languages: %s | subtitles: %s | seeders: %d | leechers: %d, | downloads: %d | trusted: %t | preferred properties: %s | priority: %d\n", + torrentPriority.ParsedTorrent.Torrent.ID, + torrentPriority.ParsedTorrent.Resolution, + strings.Join(torrentPriority.ParsedTorrent.Languages, ","), + strings.Join(torrentPriority.ParsedTorrent.Subtitles, ","), + torrentPriority.ParsedTorrent.Torrent.Seeders, + torrentPriority.ParsedTorrent.Torrent.Leechers, + torrentPriority.ParsedTorrent.Torrent.Downloads, + torrentPriority.ParsedTorrent.Torrent.Trusted, + Map2Str(torrentPriority.PreferredProperties), + torrentPriority.Priority, + ) +}