Compare commits

..

2 Commits

Author SHA1 Message Date
Timon Ringwald
de3a29ca41 main.go refactor 2022-08-25 09:04:22 +02:00
Timon Ringwald
37a41c2058 code refactor 2022-08-23 13:24:03 +02:00
9 changed files with 156 additions and 150 deletions

View File

@ -12,7 +12,7 @@ type Pair[A, B any] struct {
Second B Second B
} }
func GetAnimesToDownloadByAnimeID() (map[anilist.MediaID]*anilist.MediaList, error) { func GetAnimeListByAnimeID() (map[anilist.MediaID]*anilist.MediaList, error) {
watchingAnimesChannel, err := GetCurrentlyWatchingAnimes() watchingAnimesChannel, err := GetCurrentlyWatchingAnimes()
if err != nil { if err != nil {
return nil, err return nil, err

54
check_torrents.go Normal file
View File

@ -0,0 +1,54 @@
package main
import (
"fmt"
"time"
"git.milar.in/milarin/adverr"
)
func checkTorrents() {
fmt.Println("checking torrents")
start := time.Now()
torrents, err := GetTorrents()
if err != nil {
fmt.Println(adverr.Wrap("retrieving torrents failed", err))
return
}
animeList, err := GetAnimeListByAnimeID()
if err != nil {
fmt.Println(adverr.Wrap("retrieving anime list failed", err))
return
}
// parse torrents
parsedTorrentsByAnimeEp := ParseTorrentsByAnimeEp(torrents)
// filter not on anime list
animeListTorrents := FilterTorrentsByAnimeList(parsedTorrentsByAnimeEp, animeList)
// filter essential properties
essentialTorrents := FilterEssentialTorrents(animeListTorrents)
// filter preferred properties
preferredTorrents := GetTorrentsWithMaxPrioByAnimeEp(essentialTorrents)
for animeEp, torrentPrio := range preferredTorrents {
if IsCurrentlyDownloading(animeEp) {
continue
}
if props, inCollection := GetAnimeEpProps(animeEp); inCollection && props.Priority > torrentPrio.Priority {
continue
}
if err := DownloadTorrent(animeEp, torrentPrio.ParsedTorrent); err != nil {
panic(err) // TODO error handling
}
}
duration := time.Since(start)
fmt.Printf("\ncheck took %s. sleeping for %s\n", duration.Truncate(time.Millisecond), (PollRate - duration).Truncate(time.Millisecond))
}

View File

@ -12,7 +12,7 @@ import (
) )
func DownloadTorrent(animeEp model.AnimeEpisode, torrent *model.ParsedTorrent) error { func DownloadTorrent(animeEp model.AnimeEpisode, torrent *model.ParsedTorrent) error {
if err := SetAnimeEpDownloading(animeEp); err != nil { if err := SetCurrentlyDownloading(animeEp); err != nil {
return ErrLockFileCreationFailed.Wrap(err, animeEp.Anime.Title.Romaji, animeEp.Episode) return ErrLockFileCreationFailed.Wrap(err, animeEp.Anime.Title.Romaji, animeEp.Episode)
} }

64
envvars.go Normal file
View File

@ -0,0 +1,64 @@
package main
import (
"html/template"
"math"
"time"
"git.milar.in/animan/model"
"git.milar.in/milarin/envvars"
)
var (
PollRate = envvars.Object("POLL_RATE", 30*time.Minute, time.ParseDuration)
AnilistUsername = envvars.String("ANILIST_USERNAME", "username")
AnilistAccessToken = envvars.String("ANILIST_TOKEN", "")
StoragePath = envvars.String("STORAGE_PATH", "")
StorageUser = envvars.String("STORAGE_USER", "")
StoragePass = envvars.String("STORAGE_PASS", "")
TorrentPath = envvars.String("TORRENT_PATH", "")
AnimePath = envvars.String("ANIME_PATH", "")
AnimeEpFilepathPattern = envvars.Object(
"EPISODE_FILEPATH_PATTERN",
template.Must(template.New("anime-episode-filepath-pattern").Parse(`{{.Title.UserPreferred}}/{{.Title.UserPreferred}} Episode {{.Episode}}.{{.Ext}}`)),
template.New("anime-episode-filepath-pattern").Parse,
)
AnimeStatus = envvars.ObjectSlice("ANIME_STATUS", ",", ParseMediaListStatus)
// essential torrent properties
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", "|")
MaxSeeders = envvars.Int("MAX_SEEDERS", math.MaxInt)
MinSeeders = envvars.Int("MIN_SEEDERS", 0)
MaxLeechers = envvars.Int("MAX_LEECHERS", math.MaxInt)
MinLeechers = envvars.Int("MIN_LEECHERS", 0)
MaxDownloads = envvars.Int("MAX_DOWNLOADS", math.MaxInt)
MinDownloads = envvars.Int("MIN_DOWNLOADS", 0)
TrustedOnly = envvars.Bool("TRUSTED_ONLY", false)
// preferred torrent properties
PreferredLanguages = ParsePreferredStringProps(envvars.StringSlice("PREFERRED_LANGUAGES", "|"))
PreferredSubtitles = ParsePreferredStringProps(envvars.StringSlice("PREFERRED_SUBTITLES", "|"))
PreferredResolutions = ParsePreferredProps(envvars.StringSlice("PREFERRED_RESOLUTIONS", "|"), model.ParseResolution)
/*
TODO
PreferMoreLanguages = envvars.Bool("PREFERER_MORE_LANGUAGES", false)
PreferMoreSubtitles = envvars.Bool("PREFERER_MORE_SUBTITLES", false)
*/
)

View File

@ -41,11 +41,11 @@ func AnimeEpExistsLocally(animeEp model.AnimeEpisode) bool {
return len(files) > 0 return len(files) > 0
} }
func GetLocalAnimeEpProperties(animeEp model.AnimeEpisode) (*FilePriority, bool, error) { func GetAnimeEpProps(animeEp model.AnimeEpisode) (*FilePriority, bool) {
animeEpPath := GetAnimeEpFilepath(animeEp, "*") animeEpPath := GetAnimeEpFilepath(animeEp, "*")
files, err := filepath.Glob(animeEpPath) files, err := filepath.Glob(animeEpPath)
if err != nil { if err != nil {
return nil, false, ErrInvalidGlobSyntax.Wrap(err, animeEpPath) panic(ErrInvalidGlobSyntax.Wrap(err, animeEpPath))
} }
var mostPrio *FilePriority var mostPrio *FilePriority
@ -67,16 +67,16 @@ func GetLocalAnimeEpProperties(animeEp model.AnimeEpisode) (*FilePriority, bool,
} }
} }
return mostPrio, mostPrio != nil, nil return mostPrio, mostPrio != nil
} }
func IsAnimeEpDownloading(animeEp model.AnimeEpisode) bool { func IsCurrentlyDownloading(animeEp model.AnimeEpisode) bool {
animeEpPath := GetAnimeEpFilepath(animeEp, "lock") animeEpPath := GetAnimeEpFilepath(animeEp, "lock")
_, err := os.Stat(animeEpPath) _, err := os.Stat(animeEpPath)
return !errors.Is(err, os.ErrNotExist) return !errors.Is(err, os.ErrNotExist)
} }
func SetAnimeEpDownloading(animeEp model.AnimeEpisode) error { func SetCurrentlyDownloading(animeEp model.AnimeEpisode) error {
animeEpPath := GetAnimeEpFilepath(animeEp, "lock") animeEpPath := GetAnimeEpFilepath(animeEp, "lock")
file, err := os.Create(animeEpPath) file, err := os.Create(animeEpPath)
if err != nil { if err != nil {

131
main.go
View File

@ -2,68 +2,9 @@ package main
import ( import (
"fmt" "fmt"
"html/template"
"math"
"time" "time"
"git.milar.in/milarin/adverr"
"git.milar.in/milarin/anilist" "git.milar.in/milarin/anilist"
"git.milar.in/milarin/envvars"
"git.milar.in/nyaanime/model"
)
var (
PollRate = envvars.Object("POLL_RATE", 30*time.Minute, time.ParseDuration)
AnilistUsername = envvars.String("ANILIST_USERNAME", "username")
AnilistAccessToken = envvars.String("ANILIST_TOKEN", "")
StoragePath = envvars.String("STORAGE_PATH", "")
StorageUser = envvars.String("STORAGE_USER", "")
StoragePass = envvars.String("STORAGE_PASS", "")
TorrentPath = envvars.String("TORRENT_PATH", "")
AnimePath = envvars.String("ANIME_PATH", "")
AnimeEpFilepathPattern = envvars.Object(
"EPISODE_FILEPATH_PATTERN",
template.Must(template.New("anime-episode-filepath-pattern").Parse(`{{.Title.UserPreferred}}/{{.Title.UserPreferred}} Episode {{.Episode}}.{{.Ext}}`)),
template.New("anime-episode-filepath-pattern").Parse,
)
AnimeStatus = envvars.ObjectSlice("ANIME_STATUS", ",", ParseMediaListStatus)
// essential torrent properties
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", "|")
MaxSeeders = envvars.Int("MAX_SEEDERS", math.MaxInt)
MinSeeders = envvars.Int("MIN_SEEDERS", 0)
MaxLeechers = envvars.Int("MAX_LEECHERS", math.MaxInt)
MinLeechers = envvars.Int("MIN_LEECHERS", 0)
MaxDownloads = envvars.Int("MAX_DOWNLOADS", math.MaxInt)
MinDownloads = envvars.Int("MIN_DOWNLOADS", 0)
TrustedOnly = envvars.Bool("TRUSTED_ONLY", false)
// preferred torrent properties
PreferredLanguages = ParsePreferredStringProps(envvars.StringSlice("PREFERRED_LANGUAGES", "|"))
PreferredSubtitles = ParsePreferredStringProps(envvars.StringSlice("PREFERRED_SUBTITLES", "|"))
PreferredResolutions = ParsePreferredProps(envvars.StringSlice("PREFERRED_RESOLUTIONS", "|"), model.ParseResolution)
/*
TODO
PreferMoreLanguages = envvars.Bool("PREFERER_MORE_LANGUAGES", false)
PreferMoreSubtitles = envvars.Bool("PREFERER_MORE_SUBTITLES", false)
*/
) )
func main() { func main() {
@ -92,75 +33,3 @@ func main() {
checkTorrents() checkTorrents()
} }
} }
func checkTorrents() {
fmt.Println("checking torrents")
start := time.Now()
torrents, err := GetLatestNyaaContent()
if err != nil {
fmt.Println(adverr.Wrap("retrieving torrents failed", err))
return
}
animes, err := GetAnimesToDownloadByAnimeID()
if err != nil {
fmt.Println(adverr.Wrap("retrieving anime list failed", err))
return
}
parsedTorrents := ParseTorrentsByAnimeEpSortedByProperties(torrents)
for animeEp, torrentPriorities := range parsedTorrents {
_, animeOnList := animes[animeEp.Anime.ID]
props, found, err := GetLocalAnimeEpProperties(animeEp)
episodeInCollection := err == nil && found
fmt.Print(FormatFilePriority(animeEp, props, animeOnList, episodeInCollection, len(torrentPriorities)))
if !animeOnList {
continue
}
for _, torrentPriority := range torrentPriorities {
torrent := torrentPriority.ParsedTorrent
if IsAnimeEpDownloading(animeEp) {
fmt.Printf("%s | CURRENTLY DOWNLOADING\n", FormatTorrentPriority(torrentPriority))
continue
}
if episodeInCollection {
if props.Priority > torrentPriority.Priority {
fmt.Printf("%s | LOWER PRIORITY\n", FormatTorrentPriority(torrentPriority))
} else {
fmt.Printf("%s | HIGHER PRIORITY | STARTING DOWNLOAD\n", FormatTorrentPriority(torrentPriority))
if err := DownloadTorrent(animeEp, torrent); err != nil {
panic(err)
}
}
} else {
fmt.Printf("%s | NOT IN COLLECTION | STARTING DOWNLOAD\n", FormatTorrentPriority(torrentPriority))
if err := DownloadTorrent(animeEp, torrent); err != nil {
panic(err)
}
}
}
}
duration := time.Since(start)
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
}

View File

@ -14,7 +14,7 @@ import (
var torrentLinkRegex = regexp.MustCompile(`https:\/\/nyaa\.si\/download\/(\d+?)\.torrent`) var torrentLinkRegex = regexp.MustCompile(`https:\/\/nyaa\.si\/download\/(\d+?)\.torrent`)
func GetLatestNyaaContent() ([]model.Torrent, error) { func GetTorrents() ([]model.Torrent, error) {
resp, err := http.Get("https://nyaa.si/?page=rss&f=0&c=0_0&q=Erai-Raws+ayumu") // TODO https://nyaa.si/?page=rss resp, err := http.Get("https://nyaa.si/?page=rss&f=0&c=0_0&q=Erai-Raws+ayumu") // TODO https://nyaa.si/?page=rss
if err != nil { if err != nil {
return nil, ErrTorrentNotObtainable.Wrap(err, "torrent data acqusition failed") return nil, ErrTorrentNotObtainable.Wrap(err, "torrent data acqusition failed")

View File

@ -1,20 +1,31 @@
package main package main
import ( import (
"git.milar.in/milarin/anilist"
"git.milar.in/milarin/slices" "git.milar.in/milarin/slices"
"git.milar.in/nyaanime/model" "git.milar.in/nyaanime/model"
) )
func FilterEssentialTorrents(parsedTorrentsByAnimeEp map[model.AnimeEpisode][]*model.ParsedTorrent) map[model.AnimeEpisode][]*model.ParsedTorrent { func FilterTorrentsByAnimeList(allTorrents map[model.AnimeEpisode][]*model.ParsedTorrent, animeList map[anilist.MediaID]*anilist.MediaList) map[model.AnimeEpisode][]*model.ParsedTorrent {
filteredMap := map[model.AnimeEpisode][]*model.ParsedTorrent{} filtered := map[model.AnimeEpisode][]*model.ParsedTorrent{}
for animeEpisode, parsedTorrents := range parsedTorrentsByAnimeEp { for animeEp, torrents := range allTorrents {
if _, ok := animeList[animeEp.Anime.ID]; ok {
filtered[animeEp] = torrents
}
}
return filtered
}
func FilterEssentialTorrents(allTorrents map[model.AnimeEpisode][]*model.ParsedTorrent) map[model.AnimeEpisode][]*model.ParsedTorrent {
filtered := map[model.AnimeEpisode][]*model.ParsedTorrent{}
for animeEpisode, parsedTorrents := range allTorrents {
for _, parsedTorrent := range parsedTorrents { for _, parsedTorrent := range parsedTorrents {
if HasEssentialProperties(parsedTorrent) { if HasEssentialProperties(parsedTorrent) {
filteredMap[animeEpisode] = append(filteredMap[animeEpisode], parsedTorrent) filtered[animeEpisode] = append(filtered[animeEpisode], parsedTorrent)
} }
} }
} }
return filteredMap return filtered
} }
func HasEssentialProperties(torrent *model.ParsedTorrent) bool { func HasEssentialProperties(torrent *model.ParsedTorrent) bool {

View File

@ -1,19 +1,27 @@
package main package main
import ( import (
"sort"
"git.milar.in/milarin/slices" "git.milar.in/milarin/slices"
"git.milar.in/nyaanime/model" "git.milar.in/nyaanime/model"
) )
func SortTorrentsByPreferredProperties(torrents map[model.AnimeEpisode][]*model.ParsedTorrent) map[model.AnimeEpisode][]*TorrentPriority { func GetTorrentsWithMaxPrioByAnimeEp(torrents map[model.AnimeEpisode][]*model.ParsedTorrent) map[model.AnimeEpisode]*TorrentPriority {
torrentsWithPrio := map[model.AnimeEpisode][]*TorrentPriority{} torrentsWithPrio := map[model.AnimeEpisode]*TorrentPriority{}
for animeEp, torrentList := range torrents { for animeEp, torrentList := range torrents {
torrentPrioList := slices.Map(torrentList, NewTorrentPriority) torrentPrioList := slices.Map(torrentList, NewTorrentPriority)
sort.Slice(torrentPrioList, func(i, j int) bool { return torrentPrioList[i].Priority > torrentPrioList[j].Priority })
torrentsWithPrio[animeEp] = torrentPrioList var maxPrio *TorrentPriority
for _, torrentPrio := range torrentPrioList {
if maxPrio == nil || torrentPrio.Priority > maxPrio.Priority {
maxPrio = torrentPrio
}
}
if maxPrio != nil {
torrentsWithPrio[animeEp] = maxPrio
}
} }
return torrentsWithPrio return torrentsWithPrio