package main import ( "bytes" "context" "encoding/json" "fmt" "strconv" "strings" "time" "github.com/gookit/color" "github.com/valyala/fasthttp" ) func SearchAnimeData(query string) ([]Anime, []byte, error) { var animes []Anime data, err := searchCache.Get(query) if err != nil { err = nil dataMal, err := GetDataMal(searchApiMal + query + "&fields=id,title,main_picture,alternative_titles,start_date,end_date,synopsis,mean,rank,popularity,num_list_users,num_scoring_users,nsfw,media_type,status,genres,my_list_status,num_episodes,start_season,broadcast,source,average_episode_duration,rating,studios&limit=100") if err != nil { return nil, nil, err } var animeList AnimeListMal animes = make([]Anime, 0) err = json.Unmarshal(dataMal, &animeList) if err != nil { return nil, nil, err } // convert to anime for _, a := range animeList.Data { animes = append(animes, MalConvert(&a.Node)) } data, err = json.Marshal(animes) if err != nil { return animes, data, err } searchCache.Set(query, data) } else { err = json.Unmarshal(data, &animes) } return animes, data, err } func GetAnimeDetailData(animeId int64) (*Anime, []byte, error) { var anime Anime key := strconv.FormatInt(animeId, 10) data, err := animeCache.Get(key) if err != nil { err = nil dataMal, err := GetDataMal(animeApiMal + key + "?fields=id,title,main_picture,alternative_titles,start_date,end_date,synopsis,mean,rank,popularity,num_list_users,num_scoring_users,nsfw,media_type,status,genres,my_list_status,num_episodes,start_season,broadcast,source,average_episode_duration,rating,studios") if err != nil { return nil, nil, err } var animeMal AnimeDetailMal err = json.Unmarshal(dataMal, &animeMal) if err != nil { return nil, nil, err } // convert to anime anime = MalConvert(&animeMal) data, err = json.Marshal(&anime) if err != nil { return &anime, data, err } animeCache.Set(key, data) } else { err = json.Unmarshal(data, &anime) } return &anime, data, err } func GetUserAnimeListData(username, status string) (*AnimeListMal, []byte, error) { var list AnimeListMal data, err := animeListCache.Get(username + status) if err != nil { color.Infoln(username + "'s " + status + " abfragen...") //logOut.WriteLine("📄 " + username + "'s " + status + " abfragen...") data, err = GetDataMal(userApiMal + username + "/animelist?limit=1000&status=" + status + "&fields=list_status&nsfw=true") if err != nil { return nil, nil, err } animeListCache.Set(username+status, data) } err = json.Unmarshal(data, &list) return &list, data, err } func GetUserData(username string) (*User, []byte, error) { var user User data, err := userCache.Get(username) if err != nil { err = nil dataJikan, err := GetDataJikan(userApiJikan + username) if err != nil { return nil, nil, err } if strings.Contains(string(dataJikan), "BadResponseException") { return nil, nil, fmt.Errorf("user not found: %s", username) } var userJikan UserJikan err = json.Unmarshal(dataJikan, &userJikan) if err != nil { return nil, nil, err } // workaround number #2 bcs jikan kekw if userJikan.Data.MalID == 0 { dataJikan, err = GetDataJikan(userApiJikan + username) if err != nil { return nil, nil, err } if strings.Contains(string(dataJikan), "BadResponseException") { return nil, nil, fmt.Errorf("user not found: %s", username) } err = json.Unmarshal(dataJikan, &userJikan) if err != nil { return nil, nil, err } } if userJikan.Data.MalID == 0 { return nil, nil, fmt.Errorf("user returned empty object: %s", username) } // convert to user user = UserConvert(&userJikan) data, err = json.Marshal(&user) if err != nil { return &user, data, err } userCache.Set(username, data) } else { err = json.Unmarshal(data, &user) } return &user, data, err } func GetSeasonDataAll(season string) ([]Anime, []byte, error) { color.Infoln("Season " + season + " abfragen...") logOut.WriteLine("📺 Season " + season + " abfragen...") data, _, err := GetSeasonData(seasonStrJikan+season, 1) if err != nil { return nil, nil, err } color.Infof("%d Anime auf %d Seiten\n", data.Pagination.Items.Total, data.Pagination.LastVisiblePage) logOut.WriteLine(fmt.Sprintf("📺 %d Anime auf %d Seiten", data.Pagination.Items.Total, data.Pagination.LastVisiblePage)) animes := make([]Anime, 0) // convert to anime for _, a := range data.Data { animes = append(animes, JikanConvert(&a)) } for i := 2; data.Pagination.HasNextPage; i++ { color.Infof("Seite %d abfragen...\n", i) logOut.WriteLine(fmt.Sprintf("📺 Seite %d abfragen...", i)) newData, _, err := GetSeasonData(seasonStrJikan+season, i) if err != nil { return nil, nil, err } data.Pagination.CurrentPage = newData.Pagination.CurrentPage data.Pagination.HasNextPage = newData.Pagination.HasNextPage data.Pagination.Items.Count += newData.Pagination.Items.Count // convert to anime for _, a := range newData.Data { animes = append(animes, JikanConvert(&a)) } } color.Infof("%d Anime bekommen\n", len(animes)) logOut.WriteLine(fmt.Sprintf("📺 %d Anime bekommen", len(animes))) bytes, err := json.Marshal(animes) return animes, bytes, err } func GetSeasonData(seasonRoute string, page int) (*SeasonJikan, []byte, error) { var data SeasonJikan body, err := GetSeasonBytes(seasonRoute, page) if err != nil { return nil, body, err } err = json.Unmarshal(body, &data) return &data, body, err } func GetSeasonBytes(seasonRoute string, page int) ([]byte, error) { if page != 0 { return GetDataJikan(seasonRoute + "?page=" + strconv.Itoa(page)) } return GetDataJikan(seasonRoute) } func GetDataMal(apiAddr string) ([]byte, error) { var body []byte req := fasthttp.AcquireRequest() resp := fasthttp.AcquireResponse() defer fasthttp.ReleaseRequest(req) defer fasthttp.ReleaseResponse(resp) req.SetRequestURI(*malApiBaseUri + apiAddr) req.Header.SetMethod(fasthttp.MethodGet) req.Header.Add("X-MAL-CLIENT-ID", *malApiId) err := fasthttp.Do(req, resp) contentEncoding := resp.Header.Peek(fasthttp.HeaderContentEncoding) if bytes.EqualFold(contentEncoding, []byte("gzip")) { body, _ = resp.BodyGunzip() } else { body = resp.Body() } if resp.StatusCode() != fasthttp.StatusOK { return body, fmt.Errorf("unexpected response code: %s %d", *malApiBaseUri+apiAddr, resp.StatusCode()) } return body, err } func GetDataJikan(apiAddr string) ([]byte, error) { var body []byte ctx := context.Background() jikanLimiter.Wait(ctx) statusCode, body, err := fasthttp.Get(body, *jikanApiBaseUri+apiAddr) // retry bcs jikan kekw if statusCode == fasthttp.StatusInternalServerError { jikanLimiter.Wait(ctx) statusCode, body, err = fasthttp.Get(body, *jikanApiBaseUri+apiAddr) } if statusCode != fasthttp.StatusOK { return body, fmt.Errorf("unexpected response code: %s %d", *jikanApiBaseUri+apiAddr, statusCode) } return body, err } func GetCurrentSeasonString() string { var now = time.Now() switch now.Month() { case time.January, time.February, time.March: return fmt.Sprintf("%04d/winter", now.Year()) case time.April, time.May, time.June: return fmt.Sprintf("%04d/spring", now.Year()) case time.July, time.August, time.September: return fmt.Sprintf("%04d/summer", now.Year()) case time.October, time.November, time.December: return fmt.Sprintf("%04d/fall", now.Year()) default: return fmt.Sprintf("%04d", now.Year()) } } func GetNextSeasonString() string { var now = time.Now() switch now.Month() { case time.January, time.February, time.March: return fmt.Sprintf("%04d/spring", now.Year()) case time.April, time.May, time.June: return fmt.Sprintf("%04d/summer", now.Year()) case time.July, time.August, time.September: return fmt.Sprintf("%04d/fall", now.Year()) case time.October, time.November, time.December: return fmt.Sprintf("%04d/winter", now.Year()+1) default: return fmt.Sprintf("%04d", now.Year()) } } func GetNextNextSeasonString() string { var now = time.Now() switch now.Month() { case time.January, time.February, time.March: return fmt.Sprintf("%04d/summer", now.Year()) case time.April, time.May, time.June: return fmt.Sprintf("%04d/fall", now.Year()) case time.July, time.August, time.September: return fmt.Sprintf("%04d/winter", now.Year()+1) case time.October, time.November, time.December: return fmt.Sprintf("%04d/spring", now.Year()+1) default: return fmt.Sprintf("%04d", now.Year()) } } func GetLastSeasonString() string { var now = time.Now() switch now.Month() { case time.January, time.February, time.March: return fmt.Sprintf("%04d/fall", now.Year()-1) case time.April, time.May, time.June: return fmt.Sprintf("%04d/winter", now.Year()) case time.July, time.August, time.September: return fmt.Sprintf("%04d/spring", now.Year()) case time.October, time.November, time.December: return fmt.Sprintf("%04d/summer", now.Year()) default: return fmt.Sprintf("%04d", now.Year()) } }