diff --git a/access_token.go b/access_token.go new file mode 100644 index 0000000..3b419c7 --- /dev/null +++ b/access_token.go @@ -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 +} diff --git a/anilist.go b/anilist.go new file mode 100644 index 0000000..093983e --- /dev/null +++ b/anilist.go @@ -0,0 +1,64 @@ +package logic + +import ( + "context" + + "git.milar.in/milarin/anilist" + "git.milar.in/milarin/channel" + "git.milar.in/nyaanime/model" +) + +func GetAnimeListByAnimeID() (map[anilist.MediaID]*anilist.MediaList, error) { + watchingAnimesChannel, err := GetCurrentlyWatchingAnimes() + if err != nil { + return nil, err + } + + toMapFunc := func(entry *anilist.MediaList) (anilist.MediaID, *anilist.MediaList) { return entry.MediaID, entry } + return channel.ToMap(watchingAnimesChannel, toMapFunc), nil +} + +func GetCurrentlyWatchingAnimes() (<-chan *anilist.MediaList, error) { + token, err := GetAnilistAccessToken() + if err != nil { + return nil, ErrAnimeListNotObtainable.Wrap(err, "access token acquisition failed") + } + + media := channel.Map(channel.Of(AnimeStatuses...), func(status anilist.MediaListStatus) <-chan *anilist.MediaList { + return anilist.NewApi(token).GetMediaList(context.Background(), anilist.MediaListQuery{ + UserName: AnilistUsername, + Type: anilist.MediaTypeAnime, + Status: status, + }, nil).Chan + }) + + return channel.FlatChan(media), nil +} + +var ( + animeByTitleCache = map[string]model.Pair[*anilist.Media, error]{} +) + +func SearchAnimeByTitle(title string) (anime *anilist.Media, err error) { + // caching + if cacheEntry, ok := animeByTitleCache[title]; ok { + return cacheEntry.First, cacheEntry.Second + } + defer func() { animeByTitleCache[title] = model.Pair[*anilist.Media, error]{First: anime, Second: err} }() + + 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 +} diff --git a/envvars.go b/envvars.go index df647f1..5681d5d 100644 --- a/envvars.go +++ b/envvars.go @@ -4,11 +4,19 @@ import ( "html/template" "math" + "git.milar.in/milarin/anilist" "git.milar.in/milarin/envvars/v2" "git.milar.in/nyaanime/model" ) 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", "") AnimeEpFilepathPattern = envvars.Object( @@ -41,6 +49,11 @@ var ( 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 diff --git a/errors.go b/errors.go index 202d91b..d5421b5 100644 --- a/errors.go +++ b/errors.go @@ -3,5 +3,11 @@ package logic import "git.milar.in/milarin/adverr" var ( - ErrInvalidGlobSyntax = adverr.NewErrTmpl("ErrInvalidGlobSyntax", "invalid filepath.Glob syntax: '%s'") + 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)") ) diff --git a/go.mod b/go.mod index 241ee25..8f9d979 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,12 @@ go 1.19 require ( git.milar.in/milarin/adverr v0.2.1 - git.milar.in/milarin/anilist v1.5.0 + git.milar.in/milarin/anilist v1.5.1 + git.milar.in/milarin/channel v0.0.14 git.milar.in/milarin/envvars/v2 v2.0.0 git.milar.in/milarin/gmath v0.0.3 git.milar.in/milarin/slices v0.0.6 - git.milar.in/nyaanime/model v0.0.0-20221008104642-466e1111ddea + git.milar.in/nyaanime/model v0.0.0-20230113095840-5eb2822653c3 git.milar.in/nyaanime/parsers v0.0.0-20221207192513-e7bce7c418d8 ) diff --git a/go.sum b/go.sum index 35bccd8..1e22c8c 100644 --- a/go.sum +++ b/go.sum @@ -1,15 +1,17 @@ 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/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/anilist v1.5.1 h1:gW08WaAvXxC5/+P1QCjyfa5YtaiY9XvF5x/8G4orT6I= +git.milar.in/milarin/anilist v1.5.1/go.mod h1:8PTHXFMA45JpfRFIpcdrKwDHue8fbT/wwV1BuHFn6c0= +git.milar.in/milarin/channel v0.0.14 h1:1jBaKNNOK/mmMMgC7yVW1Cgnbb7q9aKBXZ5WnWptDek= +git.milar.in/milarin/channel v0.0.14/go.mod h1:We83LTI8S7u7II3pD+A2ChCDWJfCkcBUCUqii9HjTtM= git.milar.in/milarin/envvars/v2 v2.0.0 h1:DWRQCWaHqzDD8NGpSgv5tYLuF9A/dVFPAtTvz3oiIqE= git.milar.in/milarin/envvars/v2 v2.0.0/go.mod h1:HkdEi+gG2lJSmVq547bTlQV4qQ0hO333bE8IrE0B9yY= git.milar.in/milarin/gmath v0.0.3 h1:ii6rKNItS55O/wtIFhD1cTN2BMwDZjTBmiOocKURvxM= git.milar.in/milarin/gmath v0.0.3/go.mod h1:HDLftG5RLpiNGKiIWh+O2G1PYkNzyLDADO8Cd/1abiE= git.milar.in/milarin/slices v0.0.6 h1:AQoSarZ58WHYol9c6woWJSe8wFpPC2RC4cvIlZpfg9s= git.milar.in/milarin/slices v0.0.6/go.mod h1:NOr53AOeur/qscu/FBj3lsFR262PNYBccLYSTCAXRk4= -git.milar.in/nyaanime/model v0.0.0-20221008104642-466e1111ddea h1:iBwxI3vZ+Hix/5HHB3k9f9/R0nkwR0EvVJ3o5RJEKP4= -git.milar.in/nyaanime/model v0.0.0-20221008104642-466e1111ddea/go.mod h1:kPWLDvFrhc1Uf77gxsBOxNeJ5JTVF2HhVs1IdVcw0tg= +git.milar.in/nyaanime/model v0.0.0-20230113095840-5eb2822653c3 h1:mXcEA47FQQzeSDXE3UvhNfIt4fBfpDSq1/f0r+jbHpY= +git.milar.in/nyaanime/model v0.0.0-20230113095840-5eb2822653c3/go.mod h1:kPWLDvFrhc1Uf77gxsBOxNeJ5JTVF2HhVs1IdVcw0tg= git.milar.in/nyaanime/parsers v0.0.0-20221207192513-e7bce7c418d8 h1:vb7jasvTdan0E8VY5snnRj1Xe+60NA7Lpn+GSYE6pW0= git.milar.in/nyaanime/parsers v0.0.0-20221207192513-e7bce7c418d8/go.mod h1:GG4vtUIfxopZc/+Y8OAa//vWJw/m6aeoGN7fw6SLiEM= gopkg.in/vansante/go-ffprobe.v2 v2.1.1 h1:DIh5fMn+tlBvG7pXyUZdemVmLdERnf2xX6XOFF+0BBU= diff --git a/utils.go b/utils.go new file mode 100644 index 0000000..51221ff --- /dev/null +++ b/utils.go @@ -0,0 +1,31 @@ +package logic + +import ( + "strings" + + "git.milar.in/milarin/anilist" + "git.milar.in/milarin/slices" +) + +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 +}