initial commit
This commit is contained in:
39
access_token.go
Normal file
39
access_token.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package logic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetAnilistAccessToken() (string, error) {
|
||||||
|
if strings.HasPrefix(AnilistAccessToken, "ey") {
|
||||||
|
return AnilistAccessToken, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if StoragePath == "" {
|
||||||
|
return "", ErrAnilistTokenNotObtainable.New()
|
||||||
|
} else if StorageUser == "" || StoragePass == "" {
|
||||||
|
return "", ErrInvalidStorageParams.New()
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", StoragePath, nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", ErrStorageRequestFailed.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.SetBasicAuth(StorageUser, StoragePass)
|
||||||
|
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return "", ErrStorageRequestFailed.Wrap(err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
data, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", ErrStorageRequestFailed.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.TrimSpace(string(data)), nil
|
||||||
|
}
|
71
anilist.go
Normal file
71
anilist.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package logic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"git.tordarus.net/tordarus/anilist"
|
||||||
|
"git.tordarus.net/tordarus/channel"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetAnimeListByAnimeID(statuses []anilist.MediaListStatus) (map[anilist.MediaID]*anilist.MediaList, error) {
|
||||||
|
animeListChannel, err := GetCurrentlyWatchingAnimes(statuses...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
toMapFunc := func(entry *anilist.MediaList) (anilist.MediaID, *anilist.MediaList) { return entry.MediaID, entry }
|
||||||
|
return channel.ToMap(animeListChannel, toMapFunc), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCurrentlyWatchingAnimes(statuses ...anilist.MediaListStatus) (<-chan *anilist.MediaList, error) {
|
||||||
|
return GetCurrentlyWatchingAnimesContext(context.Background(), statuses...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCurrentlyWatchingAnimesContext(ctx context.Context, statuses ...anilist.MediaListStatus) (<-chan *anilist.MediaList, error) {
|
||||||
|
token, err := GetAnilistAccessToken()
|
||||||
|
if err != nil {
|
||||||
|
return nil, ErrAnimeListNotObtainable.Wrap(err, "access token acquisition failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
media := channel.Map(channel.Of(statuses...), func(status anilist.MediaListStatus) <-chan *anilist.MediaList {
|
||||||
|
return anilist.NewApi(token).GetMediaList(ctx, anilist.MediaListQuery{
|
||||||
|
UserName: AnilistUsername,
|
||||||
|
Type: anilist.MediaTypeAnime,
|
||||||
|
Status: status,
|
||||||
|
}, nil).Chan
|
||||||
|
})
|
||||||
|
|
||||||
|
return channel.FlatChan(media), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
animeByTitleCache = map[string]*anilist.Media{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func SearchAnimeByTitle(title string) (anime *anilist.Media, err error) {
|
||||||
|
// caching
|
||||||
|
if cacheEntry, ok := animeByTitleCache[title]; ok {
|
||||||
|
return cacheEntry, nil
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err != nil && anime != nil {
|
||||||
|
animeByTitleCache[title] = anime
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
token, err := GetAnilistAccessToken()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
anime = anilist.NewApi(token).GetMedia(context.Background(), anilist.MediaQuery{
|
||||||
|
Search: title,
|
||||||
|
Type: anilist.MediaTypeAnime,
|
||||||
|
}, nil).First()
|
||||||
|
|
||||||
|
if anime == nil {
|
||||||
|
return nil, ErrAnimeNotFound.New(title)
|
||||||
|
}
|
||||||
|
|
||||||
|
return anime, nil
|
||||||
|
}
|
31
anime_episode_filepath.go
Normal file
31
anime_episode_filepath.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package logic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.tordarus.net/nyaanime/model"
|
||||||
|
"git.tordarus.net/tordarus/anilist"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AnimePathPatternData struct {
|
||||||
|
Anime *anilist.Media
|
||||||
|
Episode int
|
||||||
|
Ext string
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAnimeEpFilepath(animeEp model.AnimeEpisode, ext string) string {
|
||||||
|
ext = strings.TrimPrefix(ext, ".")
|
||||||
|
|
||||||
|
tmplData := AnimePathPatternData{
|
||||||
|
Anime: animeEp.Anime,
|
||||||
|
Episode: animeEp.Episode,
|
||||||
|
Ext: ext,
|
||||||
|
}
|
||||||
|
|
||||||
|
b := new(strings.Builder)
|
||||||
|
if err := AnimeEpFilepathPattern.Execute(b, tmplData); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return filepath.Join(AnimePath, b.String())
|
||||||
|
}
|
28
determine_priority.go
Normal file
28
determine_priority.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package logic
|
||||||
|
|
||||||
|
import "git.tordarus.net/nyaanime/model"
|
||||||
|
|
||||||
|
func DeterminePriority(props model.PropertyHolder) (priority int, preferredProperties map[string]int) {
|
||||||
|
preferredProperties = map[string]int{}
|
||||||
|
|
||||||
|
for _, lang := range props.GetLanguages() {
|
||||||
|
if langPriority, ok := PreferredLanguages[lang]; ok {
|
||||||
|
priority += langPriority
|
||||||
|
preferredProperties["lang/"+lang] = langPriority
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sub := range props.GetSubtitles() {
|
||||||
|
if subPriority, ok := PreferredSubtitles[sub]; ok {
|
||||||
|
priority += subPriority
|
||||||
|
preferredProperties["sub/"+sub] = subPriority
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if prefRes, ok := PreferredResolutions[props.GetResolution()]; ok {
|
||||||
|
priority += prefRes
|
||||||
|
preferredProperties["res/"+props.GetResolution().String()] = prefRes
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
66
envvars.go
Normal file
66
envvars.go
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
package logic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"git.tordarus.net/nyaanime/model"
|
||||||
|
"git.tordarus.net/tordarus/anilist"
|
||||||
|
"git.tordarus.net/tordarus/envvars"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
AnilistUsername = envvars.String("ANILIST_USERNAME", "username")
|
||||||
|
AnilistAccessToken = envvars.String("ANILIST_TOKEN", "")
|
||||||
|
|
||||||
|
StoragePath = envvars.String("STORAGE_PATH", "")
|
||||||
|
StorageUser = envvars.String("STORAGE_USERNAME", "")
|
||||||
|
StoragePass = envvars.String("STORAGE_PASSWORD", "")
|
||||||
|
|
||||||
|
AnimePath = envvars.String("ANIME_PATH", "")
|
||||||
|
|
||||||
|
AnimeEpFilepathPatternStr = envvars.String("EPISODE_FILEPATH_PATTERN", `{{.Anime.Title.UserPreferred}}/{{.Anime.Title.UserPreferred}} Episode {{.Episode}}.{{.Ext}}`)
|
||||||
|
AnimeEpFilepathPattern = template.Must(template.New("EPISODE_FILEPATH_PATTERN").Parse(AnimeEpFilepathPatternStr))
|
||||||
|
|
||||||
|
// 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", "|", []string{})
|
||||||
|
EssentialSubtitles = envvars.StringSlice("ESSENTIAL_SUBTITLES", "|", []string{})
|
||||||
|
|
||||||
|
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", "|", []string{}))
|
||||||
|
PreferredSubtitles = ParsePreferredStringProps(envvars.StringSlice("PREFERRED_SUBTITLES", "|", []string{}))
|
||||||
|
PreferredResolutions = ParsePreferredProps(envvars.StringSlice("PREFERRED_RESOLUTIONS", "|", []string{}), model.ParseResolution)
|
||||||
|
|
||||||
|
AnimeStatuses = envvars.ObjectSlice("ANIME_STATUSES", ",", []anilist.MediaListStatus{
|
||||||
|
anilist.MediaListStatusCurrent,
|
||||||
|
anilist.MediaListStatusPlanning,
|
||||||
|
}, ParseMediaListStatus)
|
||||||
|
|
||||||
|
/*
|
||||||
|
TODO
|
||||||
|
|
||||||
|
better idea? implementation in torrent_sort.go (sort.Slice)
|
||||||
|
PreferredTorrents = envvars.StringSlice("PREFERRED_TORRENTS", []string{"seeders", "subtitles", "languages", "downloads"})
|
||||||
|
|
||||||
|
old idea?
|
||||||
|
PreferMoreLanguages = envvars.Bool("PREFERER_MORE_LANGUAGES", false)
|
||||||
|
PreferMoreSubtitles = envvars.Bool("PREFERER_MORE_SUBTITLES", false)
|
||||||
|
PreferMoreSeeders = envvars.Bool("PREFERER_MORE_SEEDERS", false)
|
||||||
|
PreferMoreDownloads = envvars.Bool("PREFERER_MORE_DOWNLOADS", false)
|
||||||
|
*/
|
||||||
|
)
|
13
errors.go
Normal file
13
errors.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package logic
|
||||||
|
|
||||||
|
import "git.tordarus.net/tordarus/adverr/v2"
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrInvalidGlobSyntax = adverr.NewErrTmpl("ErrInvalidGlobSyntax", "invalid filepath.Glob syntax: '%s'")
|
||||||
|
ErrAnilistTokenNotObtainable = adverr.NewErrTmpl("ErrAnilistTokenNotObtainable", "neither ANILIST_TOKEN nor STORAGE_PATH provided")
|
||||||
|
ErrAnimeListNotObtainable = adverr.NewErrTmpl("ErrAnimeListNotObtainable", "anime list from anilist.co not obtainable (reason: %s)")
|
||||||
|
ErrAnimeNotFound = adverr.NewErrTmpl("ErrAnimeNotFound", "could not find anime with name '%s'")
|
||||||
|
ErrInvalidStorageParams = adverr.NewErrTmpl("ErrInvalidStorageParams", "STORAGE_USER or STORAGE_PASS not provided")
|
||||||
|
ErrStorageRequestFailed = adverr.NewErrTmpl("ErrStorageRequestFailed", "request to file storage could not be made")
|
||||||
|
ErrInvalidAnimeStatus = adverr.NewErrTmpl("ErrInvalidAnimeStatus", "invalid status '%s' in ANIME_STATUS (allowed: %s)")
|
||||||
|
)
|
14
esc_seq.go
Normal file
14
esc_seq.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package logic
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
var EscSeqReplacer = strings.NewReplacer(
|
||||||
|
`\\`, `\`,
|
||||||
|
`\n`, "\n",
|
||||||
|
`\t`, "\t",
|
||||||
|
`\f`, "\f",
|
||||||
|
`\r`, "\r",
|
||||||
|
`\v`, "\v",
|
||||||
|
`\b`, "\b",
|
||||||
|
`\a`, "\a",
|
||||||
|
)
|
21
file_priority.go
Normal file
21
file_priority.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package logic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.tordarus.net/nyaanime/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FilePriority struct {
|
||||||
|
Properties model.PropertyHolder
|
||||||
|
Priority int
|
||||||
|
PreferredProperties map[string]int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFilePriority(props model.PropertyHolder) *FilePriority {
|
||||||
|
priority, preferredProperties := DeterminePriority(props)
|
||||||
|
|
||||||
|
return &FilePriority{
|
||||||
|
Properties: props,
|
||||||
|
Priority: priority,
|
||||||
|
PreferredProperties: preferredProperties,
|
||||||
|
}
|
||||||
|
}
|
58
file_props.go
Normal file
58
file_props.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package logic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"git.tordarus.net/nyaanime/model"
|
||||||
|
"git.tordarus.net/nyaanime/parsers"
|
||||||
|
"git.tordarus.net/tordarus/slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetAnimeEpProps(animeEp model.AnimeEpisode) (*FilePriority, bool) {
|
||||||
|
animeEpPath := GetAnimeEpFilepath(animeEp, "*")
|
||||||
|
files, err := filepath.Glob(animeEpPath)
|
||||||
|
if err != nil {
|
||||||
|
panic(ErrInvalidGlobSyntax.Wrap(err, animeEpPath))
|
||||||
|
}
|
||||||
|
|
||||||
|
var mostPrio *FilePriority
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
props, err := parsers.AnalyzeFile(file)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// if !HasFileEssentialProperties(props) {
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
|
||||||
|
fp := NewFilePriority(props)
|
||||||
|
|
||||||
|
if mostPrio == nil || fp.Priority > mostPrio.Priority {
|
||||||
|
mostPrio = fp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mostPrio, mostPrio != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func HasFileEssentialProperties(props model.PropertyHolder) bool {
|
||||||
|
if props.GetResolution() < MinResolution || props.GetResolution() > MaxResolution {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, essentialLanguage := range EssentialLanguages {
|
||||||
|
if !slices.Contains(props.GetLanguages(), essentialLanguage) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, essentialSubtitle := range EssentialSubtitles {
|
||||||
|
if !slices.Contains(props.GetSubtitles(), essentialSubtitle) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
23
go.mod
Normal file
23
go.mod
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
module git.tordarus.net/nyaanime/logic
|
||||||
|
|
||||||
|
go 1.23
|
||||||
|
|
||||||
|
toolchain go1.24.3
|
||||||
|
|
||||||
|
require (
|
||||||
|
git.tordarus.net/nyaanime/model v0.0.1
|
||||||
|
git.tordarus.net/nyaanime/parsers v0.0.2
|
||||||
|
git.tordarus.net/tordarus/adverr/v2 v2.0.2
|
||||||
|
git.tordarus.net/tordarus/anilist v1.5.2
|
||||||
|
git.tordarus.net/tordarus/channel v0.1.19
|
||||||
|
git.tordarus.net/tordarus/envvars v0.0.0-20250114175450-d73e12b838a5
|
||||||
|
git.tordarus.net/tordarus/gmath v0.0.7
|
||||||
|
git.tordarus.net/tordarus/slices v0.0.14
|
||||||
|
git.tordarus.net/tordarus/tprint v0.0.1
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||||
|
github.com/rivo/uniseg v0.2.0 // indirect
|
||||||
|
gopkg.in/vansante/go-ffprobe.v2 v2.2.1 // indirect
|
||||||
|
)
|
24
go.sum
Normal file
24
go.sum
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
git.tordarus.net/nyaanime/model v0.0.1 h1:/I+87Z6eEw/o2adltKnCk4FZai2mPekjYlzEjY1ppyQ=
|
||||||
|
git.tordarus.net/nyaanime/model v0.0.1/go.mod h1:oHV82UMNy4XgPHkI6tZiwabdi6myqHXgjMi9sNZ+rG4=
|
||||||
|
git.tordarus.net/nyaanime/parsers v0.0.2 h1:UxfDGxgS2guldLhRtlLNjst/UyeA8OC44oj7nUeRUB8=
|
||||||
|
git.tordarus.net/nyaanime/parsers v0.0.2/go.mod h1:sx8HyJCpG7zzwRAEE1ZlmVPirPc3fdArlCM5L1sxEaQ=
|
||||||
|
git.tordarus.net/tordarus/adverr/v2 v2.0.2 h1:7nvNjMMjtGPq0EY6duMiv+seJ7MacNvKSBmckHl6Erg=
|
||||||
|
git.tordarus.net/tordarus/adverr/v2 v2.0.2/go.mod h1:gCC46KsWosZJh7MVNDEU99hKQoxEWZgHITDHtmFwwiQ=
|
||||||
|
git.tordarus.net/tordarus/anilist v1.5.2 h1:SxlovS+e3lgL2SowQQwj8dQrIZzRFPomcGCw3V+My0Q=
|
||||||
|
git.tordarus.net/tordarus/anilist v1.5.2/go.mod h1:Mrhx/9+8HJVj5ebQ5fJuXqL220tEJhgQIqFK2WKPXgA=
|
||||||
|
git.tordarus.net/tordarus/channel v0.1.19 h1:d9xnSwFyvBh4B1/82mt0A7Gpm2nIZJTc+9ceJMIOu5Q=
|
||||||
|
git.tordarus.net/tordarus/channel v0.1.19/go.mod h1:8/dWFTdGO7g4AeSZ7cF6GerkGbe9c4dBVMVDBxOd9m4=
|
||||||
|
git.tordarus.net/tordarus/envvars v0.0.0-20250114175450-d73e12b838a5 h1:rKNDX/YGunqg8TEU6q1rgS2BcDKVmUW2cg61JOE/wws=
|
||||||
|
git.tordarus.net/tordarus/envvars v0.0.0-20250114175450-d73e12b838a5/go.mod h1:/qVGwrEmqtIrZyuuoIQl4vquSkPWUNJmlGNedDrdYfg=
|
||||||
|
git.tordarus.net/tordarus/gmath v0.0.7 h1:tR48idt9AUL0r556ww3ZxByTKJEr6NWCTlhl2ihzYxQ=
|
||||||
|
git.tordarus.net/tordarus/gmath v0.0.7/go.mod h1:mO7aPlvNrGVE9UFXEuuACjZgMDsM63l3OcQy6xSQnoE=
|
||||||
|
git.tordarus.net/tordarus/slices v0.0.14 h1:Jy1VRMs777WewJ7mxTgjyQIMm/Zr+co18/XoQ01YZ3A=
|
||||||
|
git.tordarus.net/tordarus/slices v0.0.14/go.mod h1:RgE7A1aSAezIvPUgcbUuMHu0q4xGKoRevT+DC0eJmwI=
|
||||||
|
git.tordarus.net/tordarus/tprint v0.0.1 h1:aM5c0nLwicUIoic/xguwE5fQdQ2bB3z0+FQEN/Yt0H4=
|
||||||
|
git.tordarus.net/tordarus/tprint v0.0.1/go.mod h1:2UdHVY/ue8vXeJU/IJY1xBikDaH35kaMzxjk9ryKB8Q=
|
||||||
|
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||||
|
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
|
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||||
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
|
gopkg.in/vansante/go-ffprobe.v2 v2.2.1 h1:sFV08OT1eZ1yroLCZVClIVd9YySgCh9eGjBWO0oRayI=
|
||||||
|
gopkg.in/vansante/go-ffprobe.v2 v2.2.1/go.mod h1:qF0AlAjk7Nqzqf3y333Ly+KxN3cKF2JqA3JT5ZheUGE=
|
53
preferred_props.go
Normal file
53
preferred_props.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package logic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.tordarus.net/tordarus/gmath"
|
||||||
|
"git.tordarus.net/tordarus/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 })
|
||||||
|
}
|
59
utils.go
Normal file
59
utils.go
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
package logic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.tordarus.net/nyaanime/model"
|
||||||
|
"git.tordarus.net/tordarus/anilist"
|
||||||
|
"git.tordarus.net/tordarus/slices"
|
||||||
|
"git.tordarus.net/tordarus/tprint"
|
||||||
|
)
|
||||||
|
|
||||||
|
var AllMediaListStatuses = []anilist.MediaListStatus{
|
||||||
|
anilist.MediaListStatusCurrent,
|
||||||
|
anilist.MediaListStatusPlanning,
|
||||||
|
anilist.MediaListStatusCompleted,
|
||||||
|
anilist.MediaListStatusDropped,
|
||||||
|
anilist.MediaListStatusPaused,
|
||||||
|
anilist.MediaListStatusRepeating,
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseMediaListStatus(str string) (anilist.MediaListStatus, error) {
|
||||||
|
s := anilist.MediaListStatus(strings.ToUpper(str))
|
||||||
|
|
||||||
|
allStatusesStr := slices.Map(AllMediaListStatuses, func(status anilist.MediaListStatus) string {
|
||||||
|
return string(status)
|
||||||
|
})
|
||||||
|
|
||||||
|
if !slices.Contains(AllMediaListStatuses, s) {
|
||||||
|
return s, ErrInvalidAnimeStatus.New(s, strings.Join(allStatusesStr, ","))
|
||||||
|
}
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Map2Table[K comparable](title string, m map[K]int) string {
|
||||||
|
table := tprint.NewTable(title, "priority")
|
||||||
|
|
||||||
|
entries := make([]model.Pair[K, int], 0, len(m))
|
||||||
|
for name, priority := range m {
|
||||||
|
entries = append(entries, model.Pair[K, int]{First: name, Second: priority})
|
||||||
|
}
|
||||||
|
sort.Slice(entries, func(i, j int) bool { return entries[i].Second > entries[j].Second })
|
||||||
|
|
||||||
|
for _, entry := range entries {
|
||||||
|
table.AddRow(entry.First, entry.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
return table.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func PrintPriorityTables() {
|
||||||
|
fmt.Println("generated priority values:")
|
||||||
|
fmt.Print(Map2Table("language", PreferredLanguages))
|
||||||
|
fmt.Print(Map2Table("subtitle", PreferredSubtitles))
|
||||||
|
fmt.Print(Map2Table("resolution", PreferredResolutions))
|
||||||
|
fmt.Println()
|
||||||
|
}
|
Reference in New Issue
Block a user