From f3ec24145da97fa7e4a5687503a6f46d59ff8c2a Mon Sep 17 00:00:00 2001 From: Vidhu Kant Sharma Date: Sat, 5 Feb 2022 21:52:30 +0530 Subject: added function to get seasonal anime and added fields support to all the endpoints that have it --- anime-searcher-2000 | Bin 0 -> 6593031 bytes anime/anime.go | 128 ++++++++++++++++++++++++++++++---------------- anime/anime.structs.go | 5 -- anime/errhandlers.go | 71 +++++++++++++++++++++++++ anime/general.structs.go | 27 ++-------- anime/seasonal.structs.go | 34 ++++++++++++ anime/validators.go | 21 ++++++++ 7 files changed, 214 insertions(+), 72 deletions(-) create mode 100755 anime-searcher-2000 create mode 100644 anime/errhandlers.go create mode 100644 anime/seasonal.structs.go diff --git a/anime-searcher-2000 b/anime-searcher-2000 new file mode 100755 index 0000000..bee2537 Binary files /dev/null and b/anime-searcher-2000 differ diff --git a/anime/anime.go b/anime/anime.go index e05b674..6c65138 100644 --- a/anime/anime.go +++ b/anime/anime.go @@ -27,25 +27,30 @@ import ( const BASE_URL string = "https://api.myanimelist.net/v2/anime" // in MAL documentation this is named Get Anime List -// TODO: handle errors (if any) -func SearchAnime(token, searchString string, limit, offset int) (AnimeSearch, error) { +func SearchAnime(token, searchString string, limit, offset int, fields []string) (AnimeSearch, error) { var searchResults AnimeSearch - // if limit exceeds what MAL supports - if limit > 500 { - return searchResults, errors.New(fmt.Sprintf("SearchAnime: Limit too high(%d). Max limit is 500", limit)) - } else if offset > 499 { - return searchResults, errors.New(fmt.Sprintf("SearchAnime: Offset too high(%d). Max offset for mal2go is 499", offset)) + // error handling for limit and offset + limitsErr := limitsErrHandler(limit, offset) + if limitsErr != nil { + log.Println(limitsErr) + } + + // handle all the errors for the fields + fields, err := fieldsErrHandler(fields) + if err != nil { + log.Println(err) } // generate endpoint url with custom params endpoint, _ := urlGenerator( BASE_URL, - []string{"q", "limit", "offset"}, - [][]string{{searchString}, {strconv.Itoa(limit)}, {strconv.Itoa(offset)}}, + []string{"q", "limit", "offset", "fields"}, + [][]string{{searchString}, {strconv.Itoa(limit)}, {strconv.Itoa(offset)}, fields}, true, ) + // gets data from API and stores it in a struct var animeSearchData AnimeSearchRaw data := requestHandler(token, endpoint) json.Unmarshal([]byte(data), &animeSearchData) @@ -69,32 +74,10 @@ func SearchAnime(token, searchString string, limit, offset int) (AnimeSearch, er func GetAnimeById(token string, animeId int, fields []string) (Anime, error) { var anime Anime - // Check if given fields are valid - for _, j := range(fields) { - if !isValidField(j) { - return anime, errors.New(fmt.Sprintf("GetAnimeById: Invalid field specified: \"%s\"", j)) - } - } - - // default fields to use when none are specified - defaultFields := []string{ - "id", "title", "main_picture", - "alternative_titles", "start_date", - "end_date", "synopsis", "mean", "rank", - "popularity", "num_list_users", - "num_scoring_users", "nsfw", "created_at", - "updated_at", "media_type", "status", - "genres", "my_list_status", "num_episodes", - "start_season", "broadcast", "source", - "average_episode_duration", "rating", - "pictures", "background", "related_anime", - "related_manga", "recommendations", - "studios", "statistics", - } - - if cap(fields) == 0 { - fields = defaultFields - log.Println("GetAnimeById: WARN: No fields specified, using all default fields to get data") + // handle all the errors for the fields + fields, err := fieldsErrHandler(fields) + if err != nil { + log.Println(err) } endpoint, _ := urlGenerator( @@ -114,14 +97,19 @@ func GetAnimeById(token string, animeId int, fields []string) (Anime, error) { } // Ranking is a list of anime sorted by their rank -func GetAnimeRanking(token string, rankingType string, limit int, offset int) (AnimeRanking, error) { +func GetAnimeRanking(token string, rankingType string, limit, offset int, fields []string) (AnimeRanking, error) { var animeRanking AnimeRanking - // if limit exceeds what MAL supports - if limit > 500 { - return animeRanking, errors.New(fmt.Sprintf("GetAnimeRanking: Limit too high(%d). Max limit is 500", limit)) - } else if offset > 499 { - return animeRanking, errors.New(fmt.Sprintf("GetAnimeRanking: Offset too high(%d). Max offset for mal2go is 499", offset)) + // error handling for limit and offset + limitsErr := limitsErrHandler(limit, offset) + if limitsErr != nil { + log.Println(limitsErr) + } + + // handle all the errors for the fields + fields, err := fieldsErrHandler(fields) + if err != nil { + log.Println(err) } // if ranking type is invalid @@ -131,8 +119,8 @@ func GetAnimeRanking(token string, rankingType string, limit int, offset int) (A endpoint, _ := urlGenerator( BASE_URL + "/ranking", - []string{"ranking_type", "limit", "offset"}, - [][]string{{rankingType}, {strconv.Itoa(limit)}, {strconv.Itoa(offset)}}, + []string{"ranking_type", "limit", "offset", "fields"}, + [][]string{{rankingType}, {strconv.Itoa(limit)}, {strconv.Itoa(offset)}, fields}, true, ) @@ -164,3 +152,57 @@ func GetAnimeRanking(token string, rankingType string, limit int, offset int) (A return animeRanking, nil } + +// get list of animes from specified season +func GetSeasonalAnime(token, year, season, sort string, limit, offset int, fields []string) (SeasonalAnime, error) { + var seasonalAnime SeasonalAnime + + // error handling for limit and offset + limitsErr := limitsErrHandler(limit, offset) + if limitsErr != nil { + log.Println(limitsErr) + } + + // handle all the errors for the fields + fields, err := fieldsErrHandler(fields) + if err != nil { + log.Println(err) + } + + // checks if valid season is specified + if !isValidSeason(season) { + return seasonalAnime, errors.New(fmt.Sprintf("GetSeasonalAnime: Invalid season specified: \"%s\"", season)) + } + + // checks if valid sort is specified + if !isValidSort(sort) { + return seasonalAnime, errors.New(fmt.Sprintf("GetSeasonalAnime: Invalid sort specified: \"%s\"", sort)) + } + + endpoint, _ := urlGenerator( + BASE_URL + fmt.Sprintf("/season/%s/%s", year, season), + []string{"sort", "limit", "offset", "fields"}, + [][]string{{sort}, {strconv.Itoa(limit)}, {strconv.Itoa(offset)}, fields}, + true, + ) + + // gets data from API and stores it in a struct + var seasonalAnimeData SeasonalAnimeRaw + data := requestHandler(token, endpoint) + json.Unmarshal([]byte(data), &seasonalAnimeData) + + // Adding all the animes to another list to get formatted results later + var animes []Anime + for _, element := range seasonalAnimeData.Data { + animes = append(animes, element.Anime) + } + + // finally generate SeasonalAnime + seasonalAnime = SeasonalAnime { + Animes: animes, + Paging: seasonalAnimeData.Paging, + Season: seasonalAnimeData.Season, + } + + return seasonalAnime, nil +} diff --git a/anime/anime.structs.go b/anime/anime.structs.go index d9d4221..1599e6d 100644 --- a/anime/anime.structs.go +++ b/anime/anime.structs.go @@ -47,11 +47,6 @@ type ListStatus struct { UpdatedAt string `json:"updated_at"` } -type Season struct { - Year int `json:"year"` - Name string `json:"season"` -} - type Broadcast struct { Day string `json:"day_of_the_week"` Time string `json:"start_time"` diff --git a/anime/errhandlers.go b/anime/errhandlers.go new file mode 100644 index 0000000..d7f70f9 --- /dev/null +++ b/anime/errhandlers.go @@ -0,0 +1,71 @@ +/* mal2go - MyAnimeList V2 API wrapper for Go + * Copyright (C) 2022 Vidhu Kant Sharma + + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ + +package anime + +import ( + "errors" + "fmt" +) + +/* NOTE: MAL still seems to send some fields + * even if they aren't requested. + * those include Title, Picture, Id, etc */ +// default fields to use when none are specified +var defaultFields []string = []string{ + "id", "title", "main_picture", + "alternative_titles", "start_date", + "end_date", "synopsis", "mean", "rank", + "popularity", "num_list_users", + "num_scoring_users", "nsfw", "created_at", + "updated_at", "media_type", "status", + "genres", "my_list_status", "num_episodes", + "start_season", "broadcast", "source", + "average_episode_duration", "rating", + "pictures", "background", "related_anime", + "related_manga", "recommendations", + "studios", "statistics", +} + +// if fields aren't specified +func fieldsErrHandler(fields []string) ([]string, error) { + if cap(fields) == 0 { + // uses all the default fields if none specified + return defaultFields, nil + } + + // checks if each given field is valid + for _, j := range(fields) { + if !isValidField(j) { + return []string{}, errors.New(fmt.Sprintf("InvalidFieldError: Invalid field specified: \"%s\"", j)) + } + } + + // everything's fine! + return fields, nil +} + +// if limit or error specified are above the limit +func limitsErrHandler(limit, offset int) error { + maxOffset := 500 - limit + if limit > 500 { + return errors.New(fmt.Sprintf("InvalidLimitError: Limit specified too high (%d > 500).", limit)) + } else if offset > maxOffset { + return errors.New(fmt.Sprintf("InvalidOffsetError: Offset specified too high (%d > %d).", offset, maxOffset)) + } + // return nil if no error + return nil +} diff --git a/anime/general.structs.go b/anime/general.structs.go index b77fbe5..c49b762 100644 --- a/anime/general.structs.go +++ b/anime/general.structs.go @@ -22,28 +22,7 @@ type ListPaging struct { PrevPage string `json:"previous"` // might need checking } -/* -// this is how the API returns data (looks horrible) -type RawRanking struct { - Data []struct { - Anime Anime `json:"node"` - Ranking struct { - Rank int `json:"rank"` - } `json:"ranking"` - } `json:"data"` - - Paging ListPaging `json:"paging"` -} - -// each anime has a ranking number -type AnimeRankingTitle struct { - Anime Anime - RankNum int -} - -// this is how mal2go returns data -type AnimeRanking struct { - Titles []AnimeRankingTitle - Paging ListPaging +type Season struct { + Year int `json:"year"` + Name string `json:"season"` } -*/ diff --git a/anime/seasonal.structs.go b/anime/seasonal.structs.go new file mode 100644 index 0000000..06bda42 --- /dev/null +++ b/anime/seasonal.structs.go @@ -0,0 +1,34 @@ +/* mal2go - MyAnimeList V2 API wrapper for Go + * Copyright (C) 2022 Vidhu Kant Sharma + + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ + +package anime + +// this is how the API returns data (looks horrible) +type SeasonalAnimeRaw struct { + Data []struct { + Anime Anime `json:"node"` + } `json:"data"` + + Paging ListPaging `json:"paging"` + Season Season `json:"season"` +} + +// this is how mal2go returns data +type SeasonalAnime struct { + Animes []Anime + Paging ListPaging + Season Season +} diff --git a/anime/validators.go b/anime/validators.go index 416f827..7f6a7cc 100644 --- a/anime/validators.go +++ b/anime/validators.go @@ -73,3 +73,24 @@ func isValidField(field string) bool { return false } +// Checks if given season is valid +func isValidSeason(season string) bool { + switch season { + case + "winter", + "spring", + "summer", + "fall": return true + } + return false +} + +// Checks if given sort is valid +func isValidSort(sort string) bool { + switch sort { + case + "anime_score", + "anime_num_list_users": return true + } + return false +} -- cgit v1.2.3