From b876d3bea001718492fa289ebeba69d2989ddaf3 Mon Sep 17 00:00:00 2001 From: Vidhu Kant Sharma Date: Sun, 30 Jan 2022 16:13:36 +0530 Subject: Added error handling for getting ranking --- anime/anime.go | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/anime/anime.go b/anime/anime.go index 5d054ab..e9ef70f 100644 --- a/anime/anime.go +++ b/anime/anime.go @@ -8,9 +8,8 @@ import ( "net/http" ) -func GetAnimeById(token string, animeId int) Anime { +func requestHandler(token string, endpoint string) string { client := &http.Client{} - endpoint := fmt.Sprintf("https://api.myanimelist.net/v2/anime/%d?fields=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", animeId) // generate request req, err := http.NewRequest("GET", endpoint, nil) @@ -18,7 +17,6 @@ func GetAnimeById(token string, animeId int) Anime { log.Fatal(err) } req.Header.Add("Authorization", token) - // req.Header.Add("Content-Type", "application/json") // do request res, err := client.Do(req) @@ -27,14 +25,45 @@ func GetAnimeById(token string, animeId int) Anime { } defer res.Body.Close() + // read body body, err := ioutil.ReadAll(res.Body) if err != nil { log.Fatal(err) } - data := string(body) + return string(body) +} + +func GetAnimeById(token string, animeId int) Anime { + endpoint := fmt.Sprintf("https://api.myanimelist.net/v2/anime/%d?fields=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", animeId) + + data := requestHandler(token, endpoint) var anime Anime json.Unmarshal([]byte(data), &anime) return anime } + +// Checks if given rankingType is valid +func isValidRankingType(rankingType string) bool { + switch rankingType { + case + "all", + "airing", + "upcoming", + "tv", + "ova", + "movie", + "special", + "bypopularity", + "favorite": + return true + } + return false +} + +func GetAnimeRanking(token string, rankingType string) { + if !isValidRankingType(rankingType) { + log.Fatal(fmt.Sprintf("GetAnimeRanking: Invalid Ranking Type Given (\"%s\")", rankingType)) + } +} -- cgit v1.2.3 From 02752551dec484dd0e2b6f50158f516fd5d5c39d Mon Sep 17 00:00:00 2001 From: Vidhu Kant Sharma Date: Mon, 31 Jan 2022 00:06:46 +0530 Subject: Completed Get Anime Ranking API route --- anime/anime.go | 40 +++++++++++++++++++-- anime/anime.structs.go | 92 ++++++++++++++++++++++++++++++++++++++++++++++++ anime/ranking.structs.go | 31 ++++++++++++++++ anime/structs.go | 92 ------------------------------------------------ 4 files changed, 160 insertions(+), 95 deletions(-) create mode 100644 anime/anime.structs.go create mode 100644 anime/ranking.structs.go delete mode 100644 anime/structs.go diff --git a/anime/anime.go b/anime/anime.go index e9ef70f..c397d93 100644 --- a/anime/anime.go +++ b/anime/anime.go @@ -6,6 +6,7 @@ import ( "io/ioutil" "log" "net/http" + "errors" ) func requestHandler(token string, endpoint string) string { @@ -34,11 +35,12 @@ func requestHandler(token string, endpoint string) string { return string(body) } +// Each anime has its own ID on MAL func GetAnimeById(token string, animeId int) Anime { endpoint := fmt.Sprintf("https://api.myanimelist.net/v2/anime/%d?fields=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", animeId) - data := requestHandler(token, endpoint) var anime Anime + data := requestHandler(token, endpoint) json.Unmarshal([]byte(data), &anime) return anime @@ -62,8 +64,40 @@ func isValidRankingType(rankingType string) bool { return false } -func GetAnimeRanking(token string, rankingType string) { +// Ranking is a list of anime sorted by their rank +func GetAnimeRanking(token string, rankingType string) (AnimeRanking, error) { + var animeRanking AnimeRanking if !isValidRankingType(rankingType) { - log.Fatal(fmt.Sprintf("GetAnimeRanking: Invalid Ranking Type Given (\"%s\")", rankingType)) + return animeRanking, errors.New(fmt.Sprintf("GetAnimeRanking: Invalid Ranking Type Given (\"%s\")", rankingType)) + } + + endpoint := "https://api.myanimelist.net/v2/anime/ranking?ranking_type=all&limit=4" + + // gets data from API and stores it in a struct + var rankingData RawRanking + data := requestHandler(token, endpoint) + json.Unmarshal([]byte(data), &rankingData) + + // Adding all the animes in ranking list to a slice + var animeRankingTitles []AnimeRankingTitle + for _, element := range rankingData.Data { + animeRankingTitles = append( + animeRankingTitles, + AnimeRankingTitle { + Anime: element.Anime, + RankNum: element.Ranking.Rank, + }, + ) } + + // Finally, create the AnimeRanking object + animeRanking = AnimeRanking { + Titles: animeRankingTitles, + Paging: ListPaging { + NextPage: rankingData.Paging.NextPage, + PrevPage: rankingData.Paging.PrevPage, + }, + } + + return animeRanking, nil } diff --git a/anime/anime.structs.go b/anime/anime.structs.go new file mode 100644 index 0000000..bb35ded --- /dev/null +++ b/anime/anime.structs.go @@ -0,0 +1,92 @@ +package anime + +type AnimePicture struct { + Medium string `json:"large"` + Large string `json:"medium"` +} + +type StatusStatistics struct { + Watching int `json:"watching"` + Completed int `json:"completed"` + OnHold int `json:"on_hold"` + Dropped int `json:"dropped"` + PlanToWatch int `json:"plan_to_watch"` +} + +type AnimeStatistics struct { + Status StatusStatistics `json:"status"` + NumListUsers int `json:"num_list_users"` +} + +type Genre struct { + Id int `json:"id"` + Name string `json:"name"` +} + +type ListStatus struct { + Status string `json:"status"` + Score int `json:"score"` + EpWatched int `json:"num_episodes_watched"` + IsRewatching bool `json:"is_rewatching"` + 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"` +} + +type Related struct { + Anime Anime `json:"node"` + RelationType string `json:"relation_type"` + RelationTypeFormatted string `json:"relation_type_formatted"` +} + +type Studio struct { + Id int `json:"id"` + Name string `json:"name"` +} + +type Recommendation struct { + Anime Anime `json:"node"` + Num int `json:"num_recommendations"` +} + +type Anime struct { + Id int `json:"id"` + Title string `json:"title"` + MainPicture AnimePicture `json:"main_picture"` + AltTitles []string `json:"alternative_titles"` + StartDate string `json:"start_date"` + EndDate string `json:"end_date"` + Synopsis string `json:"synopsis"` + MeanScore float32 `json:"mean"` + Rank int `json:"rank"` + Popularity int `json:"popularity"` + NumListUsers int `json:"num_list_users"` + NumScoringUsers int `json:"num_scoring_users"` + NsfwStatus string `json:"nsfw"` // find out what values are there + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` + MediaType string `json:"media_type"` + Status string `json:"status"` + Genres []Genre `json:"genres"` + MyListStatus ListStatus `json:"my_list_status"` + NumEpisodes int `json:"num_episodes"` + StartSeason Season `json:"start_season"` + Broadcast Broadcast `json:"broadcast"` + Source string `json:"source"` + DurationSeconds int `json:"average_episode_duration"` + Rating string `json:"rating"` + Pictures []AnimePicture `json:"pictures"` + Background string `json:"background"` + RelatedAnime []Related `json:"related_anime"` + Recommendations []Recommendation `json:"recommendations"` + Studios []Studio `json:"studios"` + Statistics AnimeStatistics `json:"statistics"` +} diff --git a/anime/ranking.structs.go b/anime/ranking.structs.go new file mode 100644 index 0000000..8b9b303 --- /dev/null +++ b/anime/ranking.structs.go @@ -0,0 +1,31 @@ +package anime + +// contains previous/next page for anime list +type ListPaging struct { + NextPage string `json:"next"` + 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 +} diff --git a/anime/structs.go b/anime/structs.go deleted file mode 100644 index bb35ded..0000000 --- a/anime/structs.go +++ /dev/null @@ -1,92 +0,0 @@ -package anime - -type AnimePicture struct { - Medium string `json:"large"` - Large string `json:"medium"` -} - -type StatusStatistics struct { - Watching int `json:"watching"` - Completed int `json:"completed"` - OnHold int `json:"on_hold"` - Dropped int `json:"dropped"` - PlanToWatch int `json:"plan_to_watch"` -} - -type AnimeStatistics struct { - Status StatusStatistics `json:"status"` - NumListUsers int `json:"num_list_users"` -} - -type Genre struct { - Id int `json:"id"` - Name string `json:"name"` -} - -type ListStatus struct { - Status string `json:"status"` - Score int `json:"score"` - EpWatched int `json:"num_episodes_watched"` - IsRewatching bool `json:"is_rewatching"` - 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"` -} - -type Related struct { - Anime Anime `json:"node"` - RelationType string `json:"relation_type"` - RelationTypeFormatted string `json:"relation_type_formatted"` -} - -type Studio struct { - Id int `json:"id"` - Name string `json:"name"` -} - -type Recommendation struct { - Anime Anime `json:"node"` - Num int `json:"num_recommendations"` -} - -type Anime struct { - Id int `json:"id"` - Title string `json:"title"` - MainPicture AnimePicture `json:"main_picture"` - AltTitles []string `json:"alternative_titles"` - StartDate string `json:"start_date"` - EndDate string `json:"end_date"` - Synopsis string `json:"synopsis"` - MeanScore float32 `json:"mean"` - Rank int `json:"rank"` - Popularity int `json:"popularity"` - NumListUsers int `json:"num_list_users"` - NumScoringUsers int `json:"num_scoring_users"` - NsfwStatus string `json:"nsfw"` // find out what values are there - CreatedAt string `json:"created_at"` - UpdatedAt string `json:"updated_at"` - MediaType string `json:"media_type"` - Status string `json:"status"` - Genres []Genre `json:"genres"` - MyListStatus ListStatus `json:"my_list_status"` - NumEpisodes int `json:"num_episodes"` - StartSeason Season `json:"start_season"` - Broadcast Broadcast `json:"broadcast"` - Source string `json:"source"` - DurationSeconds int `json:"average_episode_duration"` - Rating string `json:"rating"` - Pictures []AnimePicture `json:"pictures"` - Background string `json:"background"` - RelatedAnime []Related `json:"related_anime"` - Recommendations []Recommendation `json:"recommendations"` - Studios []Studio `json:"studios"` - Statistics AnimeStatistics `json:"statistics"` -} -- cgit v1.2.3 From a835f9b0b8b714a76d8b2f9c49b84f7042ddbd6a Mon Sep 17 00:00:00 2001 From: Vidhu Kant Sharma Date: Mon, 31 Jan 2022 10:35:43 +0530 Subject: distributed code among multiple files for simplicity --- anime/anime.go | 47 -------------------------------------- anime/anime.structs.go | 1 + anime/util.go | 33 +++++++++++++++++++++++++++ anime/validators.go | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 95 insertions(+), 47 deletions(-) create mode 100644 anime/util.go create mode 100644 anime/validators.go diff --git a/anime/anime.go b/anime/anime.go index c397d93..7e5cf70 100644 --- a/anime/anime.go +++ b/anime/anime.go @@ -3,38 +3,9 @@ package anime import ( "encoding/json" "fmt" - "io/ioutil" - "log" - "net/http" "errors" ) -func requestHandler(token string, endpoint string) string { - client := &http.Client{} - - // generate request - req, err := http.NewRequest("GET", endpoint, nil) - if err != nil { - log.Fatal(err) - } - req.Header.Add("Authorization", token) - - // do request - res, err := client.Do(req) - if err != nil { - log.Fatal(err) - } - defer res.Body.Close() - - // read body - body, err := ioutil.ReadAll(res.Body) - if err != nil { - log.Fatal(err) - } - - return string(body) -} - // Each anime has its own ID on MAL func GetAnimeById(token string, animeId int) Anime { endpoint := fmt.Sprintf("https://api.myanimelist.net/v2/anime/%d?fields=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", animeId) @@ -46,24 +17,6 @@ func GetAnimeById(token string, animeId int) Anime { return anime } -// Checks if given rankingType is valid -func isValidRankingType(rankingType string) bool { - switch rankingType { - case - "all", - "airing", - "upcoming", - "tv", - "ova", - "movie", - "special", - "bypopularity", - "favorite": - return true - } - return false -} - // Ranking is a list of anime sorted by their rank func GetAnimeRanking(token string, rankingType string) (AnimeRanking, error) { var animeRanking AnimeRanking diff --git a/anime/anime.structs.go b/anime/anime.structs.go index bb35ded..a5e7d8e 100644 --- a/anime/anime.structs.go +++ b/anime/anime.structs.go @@ -61,6 +61,7 @@ type Anime struct { Id int `json:"id"` Title string `json:"title"` MainPicture AnimePicture `json:"main_picture"` + // TODO: AltTitles should also have options for JP and EN Titles AltTitles []string `json:"alternative_titles"` StartDate string `json:"start_date"` EndDate string `json:"end_date"` diff --git a/anime/util.go b/anime/util.go new file mode 100644 index 0000000..319bbc9 --- /dev/null +++ b/anime/util.go @@ -0,0 +1,33 @@ +package anime + +import ( + "io/ioutil" + "log" + "net/http" +) + +func requestHandler(token string, endpoint string) string { + client := &http.Client{} + + // generate request + req, err := http.NewRequest("GET", endpoint, nil) + if err != nil { + log.Fatal(err) + } + req.Header.Add("Authorization", token) + + // do request + res, err := client.Do(req) + if err != nil { + log.Fatal(err) + } + defer res.Body.Close() + + // read body + body, err := ioutil.ReadAll(res.Body) + if err != nil { + log.Fatal(err) + } + + return string(body) +} diff --git a/anime/validators.go b/anime/validators.go new file mode 100644 index 0000000..3f66abe --- /dev/null +++ b/anime/validators.go @@ -0,0 +1,61 @@ +package anime + +// Checks if given rankingType is valid +func isValidRankingType(rankingType string) bool { + switch rankingType { + case + "all", + "airing", + "upcoming", + "tv", + "ova", + "movie", + "special", + "bypopularity", + "favorite": + return true + } + return false +} + +// Checks if given rankingType is valid +func areValidFields(field string) bool { + switch field { + case + "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", + "avarage_episode_duration", + "rating", + "pictures", + "background", + "related_anime", + "related_manga", + "recommendations", + "studios", + "statistics": + return true + } + return false +} + -- cgit v1.2.3 From 0dd65dd062362f913b3028e07e2f0c3afaec8894 Mon Sep 17 00:00:00 2001 From: Vidhu Kant Sharma Date: Mon, 31 Jan 2022 11:25:04 +0530 Subject: Added a URL Generator --- anime/anime.go | 12 +++++++++--- anime/util.go | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/anime/anime.go b/anime/anime.go index 7e5cf70..2c23209 100644 --- a/anime/anime.go +++ b/anime/anime.go @@ -2,8 +2,9 @@ package anime import ( "encoding/json" + "errors" "fmt" - "errors" + "strconv" ) // Each anime has its own ID on MAL @@ -18,13 +19,18 @@ func GetAnimeById(token string, animeId int) Anime { } // Ranking is a list of anime sorted by their rank -func GetAnimeRanking(token string, rankingType string) (AnimeRanking, error) { +func GetAnimeRanking(token string, rankingType string, limit int) (AnimeRanking, error) { var animeRanking AnimeRanking if !isValidRankingType(rankingType) { return animeRanking, errors.New(fmt.Sprintf("GetAnimeRanking: Invalid Ranking Type Given (\"%s\")", rankingType)) } - endpoint := "https://api.myanimelist.net/v2/anime/ranking?ranking_type=all&limit=4" + endpoint, _ := urlGenerator( + "https://api.myanimelist.net/v2/anime/ranking", + []string{"ranking_type", "limit"}, + [][]string{{rankingType}, {strconv.Itoa(limit)}}, + true, + ) // gets data from API and stores it in a struct var rankingData RawRanking diff --git a/anime/util.go b/anime/util.go index 319bbc9..f09f189 100644 --- a/anime/util.go +++ b/anime/util.go @@ -4,8 +4,11 @@ import ( "io/ioutil" "log" "net/http" + "errors" ) +// Handles HTTP request with your OAuth token as a Header +// TODO: Verify that this function is safe to use func requestHandler(token string, endpoint string) string { client := &http.Client{} @@ -31,3 +34,40 @@ func requestHandler(token string, endpoint string) string { return string(body) } + +func urlGenerator(baseUrl string, names []string, values [][]string, isPrimary bool) (string, error) { + // TODO: error if cap(names) != cap(values) + if cap(names) != cap(values) { + return "", errors.New("urlGenerator: Error: Length of names and values don't match.") + } + + var fields string + + for index, name := range(names) { + var data string + /* if the data is the first field in URL, + * it goes like ?key=value + * else it is &nextkey=value */ + if isPrimary { + data = "?" + name + "=" + } else { + data = "&" + name + "=" + } + + // add values to data variable + for i, j := range values[index] { + if i > 0 { + data = data + "," + j + } else { + data = data + j + } + } + + fields = fields + data + + // from now on all other fields will be secondary + isPrimary = false + } + + return baseUrl + fields, nil +} -- cgit v1.2.3 From c4c273888446b4beb215cafc165ecd9365bee357 Mon Sep 17 00:00:00 2001 From: Vidhu Kant Sharma Date: Mon, 31 Jan 2022 12:00:19 +0530 Subject: added error handling for GetAnimeById --- anime/anime.go | 52 ++++++++++++++++++++++++++++++++++++++++++++++------ anime/util.go | 4 ++-- anime/validators.go | 10 ++++------ 3 files changed, 52 insertions(+), 14 deletions(-) diff --git a/anime/anime.go b/anime/anime.go index 2c23209..bad3cf3 100644 --- a/anime/anime.go +++ b/anime/anime.go @@ -4,29 +4,69 @@ import ( "encoding/json" "errors" "fmt" + "log" "strconv" ) -// Each anime has its own ID on MAL -func GetAnimeById(token string, animeId int) Anime { - endpoint := fmt.Sprintf("https://api.myanimelist.net/v2/anime/%d?fields=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", animeId) +const BASE_URL string = "https://api.myanimelist.net/v2/anime" +// Each anime has its own ID on MAL +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") + } + + endpoint, _ := urlGenerator( + BASE_URL + "/" + strconv.Itoa(animeId), + []string{"fields"}, + /* it seems to still return all fields from the API. + * this might be an issue with MAL itself + * TODO: look into this */ + [][]string{fields}, + true, + ) + data := requestHandler(token, endpoint) json.Unmarshal([]byte(data), &anime) - return anime + return anime, nil } // Ranking is a list of anime sorted by their rank func GetAnimeRanking(token string, rankingType string, limit int) (AnimeRanking, error) { var animeRanking AnimeRanking if !isValidRankingType(rankingType) { - return animeRanking, errors.New(fmt.Sprintf("GetAnimeRanking: Invalid Ranking Type Given (\"%s\")", rankingType)) + return animeRanking, errors.New(fmt.Sprintf("GetAnimeRanking: Invalid ranking type specified: \"%s\"", rankingType)) } endpoint, _ := urlGenerator( - "https://api.myanimelist.net/v2/anime/ranking", + BASE_URL + "/ranking", []string{"ranking_type", "limit"}, [][]string{{rankingType}, {strconv.Itoa(limit)}}, true, diff --git a/anime/util.go b/anime/util.go index f09f189..34ba863 100644 --- a/anime/util.go +++ b/anime/util.go @@ -36,7 +36,7 @@ func requestHandler(token string, endpoint string) string { } func urlGenerator(baseUrl string, names []string, values [][]string, isPrimary bool) (string, error) { - // TODO: error if cap(names) != cap(values) + // length of names and values should be same if cap(names) != cap(values) { return "", errors.New("urlGenerator: Error: Length of names and values don't match.") } @@ -50,7 +50,7 @@ func urlGenerator(baseUrl string, names []string, values [][]string, isPrimary b * else it is &nextkey=value */ if isPrimary { data = "?" + name + "=" - } else { + } else { data = "&" + name + "=" } diff --git a/anime/validators.go b/anime/validators.go index 3f66abe..4cf4986 100644 --- a/anime/validators.go +++ b/anime/validators.go @@ -12,14 +12,13 @@ func isValidRankingType(rankingType string) bool { "movie", "special", "bypopularity", - "favorite": - return true + "favorite": return true } return false } // Checks if given rankingType is valid -func areValidFields(field string) bool { +func isValidField(field string) bool { switch field { case "id", @@ -45,7 +44,7 @@ func areValidFields(field string) bool { "start_season", "broadcast", "source", - "avarage_episode_duration", + "average_episode_duration", "rating", "pictures", "background", @@ -53,8 +52,7 @@ func areValidFields(field string) bool { "related_manga", "recommendations", "studios", - "statistics": - return true + "statistics": return true } return false } -- cgit v1.2.3 From caa17299f9f70addca805eb94a8174efcdda6985 Mon Sep 17 00:00:00 2001 From: Vidhu Kant Sharma Date: Mon, 31 Jan 2022 12:06:52 +0530 Subject: Adding copyright declarations to every file --- LICENSE | 4 ++-- anime/anime.go | 16 ++++++++++++++++ anime/anime.structs.go | 16 ++++++++++++++++ anime/ranking.structs.go | 16 ++++++++++++++++ anime/util.go | 16 ++++++++++++++++ anime/validators.go | 16 ++++++++++++++++ 6 files changed, 82 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index f288702..29a48b1 100644 --- a/LICENSE +++ b/LICENSE @@ -631,8 +631,8 @@ to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. - - Copyright (C) + 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 diff --git a/anime/anime.go b/anime/anime.go index bad3cf3..1439221 100644 --- a/anime/anime.go +++ b/anime/anime.go @@ -1,3 +1,19 @@ +/* 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 ( diff --git a/anime/anime.structs.go b/anime/anime.structs.go index a5e7d8e..596f16e 100644 --- a/anime/anime.structs.go +++ b/anime/anime.structs.go @@ -1,3 +1,19 @@ +/* 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 type AnimePicture struct { diff --git a/anime/ranking.structs.go b/anime/ranking.structs.go index 8b9b303..faa9722 100644 --- a/anime/ranking.structs.go +++ b/anime/ranking.structs.go @@ -1,3 +1,19 @@ +/* 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 // contains previous/next page for anime list diff --git a/anime/util.go b/anime/util.go index 34ba863..f1f3b2a 100644 --- a/anime/util.go +++ b/anime/util.go @@ -1,3 +1,19 @@ +/* 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 ( diff --git a/anime/validators.go b/anime/validators.go index 4cf4986..416f827 100644 --- a/anime/validators.go +++ b/anime/validators.go @@ -1,3 +1,19 @@ +/* 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 // Checks if given rankingType is valid -- cgit v1.2.3 From 97a02c2d9b309ae7e6ed2c0b807ed02913ded98c Mon Sep 17 00:00:00 2001 From: Vidhu Kant Sharma Date: Mon, 31 Jan 2022 12:21:19 +0530 Subject: Added error handling for if limit is exceeded --- anime/anime.go | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/anime/anime.go b/anime/anime.go index 1439221..5d207ea 100644 --- a/anime/anime.go +++ b/anime/anime.go @@ -21,6 +21,7 @@ import ( "errors" "fmt" "log" + "math" "strconv" ) @@ -75,16 +76,25 @@ 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) (AnimeRanking, error) { +func GetAnimeRanking(token string, rankingType string, limit int, offset int) (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)) + } + + // if ranking type is invalid if !isValidRankingType(rankingType) { return animeRanking, errors.New(fmt.Sprintf("GetAnimeRanking: Invalid ranking type specified: \"%s\"", rankingType)) } endpoint, _ := urlGenerator( BASE_URL + "/ranking", - []string{"ranking_type", "limit"}, - [][]string{{rankingType}, {strconv.Itoa(limit)}}, + []string{"ranking_type", "limit", "offset"}, + [][]string{{rankingType}, {strconv.Itoa(limit)}, {strconv.Itoa(offset)}}, true, ) -- cgit v1.2.3 From f2d4bf9de9808f08f523ea2c741f385830960214 Mon Sep 17 00:00:00 2001 From: Vidhu Kant Sharma Date: Mon, 31 Jan 2022 22:07:21 +0530 Subject: Fixed Alternate Titles not loading --- anime/anime.go | 1 - anime/anime.structs.go | 10 ++++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/anime/anime.go b/anime/anime.go index 5d207ea..34e996c 100644 --- a/anime/anime.go +++ b/anime/anime.go @@ -21,7 +21,6 @@ import ( "errors" "fmt" "log" - "math" "strconv" ) diff --git a/anime/anime.structs.go b/anime/anime.structs.go index 596f16e..3b468d1 100644 --- a/anime/anime.structs.go +++ b/anime/anime.structs.go @@ -73,12 +73,17 @@ type Recommendation struct { Num int `json:"num_recommendations"` } +type AltTitles struct { + Synonyms []string `json:"synonyms"` + En string `json:"en"` + Ja string `json:"ja"` +} + type Anime struct { Id int `json:"id"` Title string `json:"title"` MainPicture AnimePicture `json:"main_picture"` - // TODO: AltTitles should also have options for JP and EN Titles - AltTitles []string `json:"alternative_titles"` + AltTitles AltTitles `json:"alternative_titles"` StartDate string `json:"start_date"` EndDate string `json:"end_date"` Synopsis string `json:"synopsis"` @@ -99,6 +104,7 @@ type Anime struct { Broadcast Broadcast `json:"broadcast"` Source string `json:"source"` DurationSeconds int `json:"average_episode_duration"` + // Rating as in R, PG13, etc Rating string `json:"rating"` Pictures []AnimePicture `json:"pictures"` Background string `json:"background"` -- cgit v1.2.3 From a01c567bf41778a5ca4c7d5b77eb375d4f058d63 Mon Sep 17 00:00:00 2001 From: Vidhu Kant Sharma Date: Sat, 5 Feb 2022 13:33:00 +0530 Subject: fixed Statistics.Status.* showing 0 instead of correct data --- anime/anime.structs.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/anime/anime.structs.go b/anime/anime.structs.go index 3b468d1..2db1b19 100644 --- a/anime/anime.structs.go +++ b/anime/anime.structs.go @@ -22,11 +22,11 @@ type AnimePicture struct { } type StatusStatistics struct { - Watching int `json:"watching"` - Completed int `json:"completed"` - OnHold int `json:"on_hold"` - Dropped int `json:"dropped"` - PlanToWatch int `json:"plan_to_watch"` + Watching string `json:"watching"` + Completed string `json:"completed"` + OnHold string `json:"on_hold"` + Dropped string `json:"dropped"` + PlanToWatch string `json:"plan_to_watch"` } type AnimeStatistics struct { -- cgit v1.2.3 From b876e67c2b21631231a06a4f7c929cb212d01595 Mon Sep 17 00:00:00 2001 From: Vidhu Kant Sharma Date: Sat, 5 Feb 2022 14:18:46 +0530 Subject: implemented searching to MAL --- anime/anime.go | 39 ++++++++++++++++++++++++++++++++++++++ anime/anime.structs.go | 6 +++++- anime/general.structs.go | 49 ++++++++++++++++++++++++++++++++++++++++++++++++ anime/ranking.structs.go | 6 ------ anime/search.structs.go | 32 +++++++++++++++++++++++++++++++ 5 files changed, 125 insertions(+), 7 deletions(-) create mode 100644 anime/general.structs.go create mode 100644 anime/search.structs.go diff --git a/anime/anime.go b/anime/anime.go index 34e996c..e05b674 100644 --- a/anime/anime.go +++ b/anime/anime.go @@ -26,6 +26,45 @@ 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) { + 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)) + } + + // generate endpoint url with custom params + endpoint, _ := urlGenerator( + BASE_URL, + []string{"q", "limit", "offset"}, + [][]string{{searchString}, {strconv.Itoa(limit)}, {strconv.Itoa(offset)}}, + true, + ) + + var animeSearchData AnimeSearchRaw + data := requestHandler(token, endpoint) + json.Unmarshal([]byte(data), &animeSearchData) + + // Adding all the animes to another list to get formatted results later + var animes []Anime + for _, element := range animeSearchData.Data { + animes = append(animes, element.Anime) + } + + // finally generate AnimeList + searchResults = AnimeSearch { + Animes: animes, + Paging: animeSearchData.Paging, + } + + return searchResults, nil +} + // Each anime has its own ID on MAL func GetAnimeById(token string, animeId int, fields []string) (Anime, error) { var anime Anime diff --git a/anime/anime.structs.go b/anime/anime.structs.go index 2db1b19..d9d4221 100644 --- a/anime/anime.structs.go +++ b/anime/anime.structs.go @@ -92,7 +92,11 @@ type Anime struct { Popularity int `json:"popularity"` NumListUsers int `json:"num_list_users"` NumScoringUsers int `json:"num_scoring_users"` - NsfwStatus string `json:"nsfw"` // find out what values are there + /* NsfwStatus potential values: + * white = sfw + * gray = probably nsfw + * black = nsfw */ + NsfwStatus string `json:"nsfw"` CreatedAt string `json:"created_at"` UpdatedAt string `json:"updated_at"` MediaType string `json:"media_type"` diff --git a/anime/general.structs.go b/anime/general.structs.go new file mode 100644 index 0000000..b77fbe5 --- /dev/null +++ b/anime/general.structs.go @@ -0,0 +1,49 @@ +/* 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 + +// contains previous/next page for anime list +type ListPaging struct { + NextPage string `json:"next"` + 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 +} +*/ diff --git a/anime/ranking.structs.go b/anime/ranking.structs.go index faa9722..28ae860 100644 --- a/anime/ranking.structs.go +++ b/anime/ranking.structs.go @@ -16,12 +16,6 @@ package anime -// contains previous/next page for anime list -type ListPaging struct { - NextPage string `json:"next"` - PrevPage string `json:"previous"` // might need checking -} - // this is how the API returns data (looks horrible) type RawRanking struct { Data []struct { diff --git a/anime/search.structs.go b/anime/search.structs.go new file mode 100644 index 0000000..e59bb44 --- /dev/null +++ b/anime/search.structs.go @@ -0,0 +1,32 @@ +/* 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 AnimeSearchRaw struct { + Data []struct { + Anime Anime `json:"node"` + } `json:"data"` + + Paging ListPaging `json:"paging"` +} + +// this is how mal2go returns data +type AnimeSearch struct { + Animes []Anime + Paging ListPaging +} -- cgit v1.2.3 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 From e550d3d71360c4da68c7f9e978ef45f05e68b216 Mon Sep 17 00:00:00 2001 From: Vidhu Kant Sharma Date: Sat, 5 Feb 2022 22:15:48 +0530 Subject: implemented function for get suggested anime endpoint --- anime/anime.go | 58 +++++++++++++++++++++++++++++++++++------ anime/suggestedanime.structs.go | 31 ++++++++++++++++++++++ 2 files changed, 81 insertions(+), 8 deletions(-) create mode 100644 anime/suggestedanime.structs.go diff --git a/anime/anime.go b/anime/anime.go index 6c65138..3344016 100644 --- a/anime/anime.go +++ b/anime/anime.go @@ -20,7 +20,6 @@ import ( "encoding/json" "errors" "fmt" - "log" "strconv" ) @@ -33,13 +32,13 @@ func SearchAnime(token, searchString string, limit, offset int, fields []string) // error handling for limit and offset limitsErr := limitsErrHandler(limit, offset) if limitsErr != nil { - log.Println(limitsErr) + return searchResults, limitsErr } // handle all the errors for the fields fields, err := fieldsErrHandler(fields) if err != nil { - log.Println(err) + return searchResults, err } // generate endpoint url with custom params @@ -77,7 +76,7 @@ func GetAnimeById(token string, animeId int, fields []string) (Anime, error) { // handle all the errors for the fields fields, err := fieldsErrHandler(fields) if err != nil { - log.Println(err) + return anime, err } endpoint, _ := urlGenerator( @@ -103,13 +102,13 @@ func GetAnimeRanking(token string, rankingType string, limit, offset int, fields // error handling for limit and offset limitsErr := limitsErrHandler(limit, offset) if limitsErr != nil { - log.Println(limitsErr) + return animeRanking, limitsErr } // handle all the errors for the fields fields, err := fieldsErrHandler(fields) if err != nil { - log.Println(err) + return animeRanking, err } // if ranking type is invalid @@ -160,13 +159,13 @@ func GetSeasonalAnime(token, year, season, sort string, limit, offset int, field // error handling for limit and offset limitsErr := limitsErrHandler(limit, offset) if limitsErr != nil { - log.Println(limitsErr) + return seasonalAnime, limitsErr } // handle all the errors for the fields fields, err := fieldsErrHandler(fields) if err != nil { - log.Println(err) + return seasonalAnime, err } // checks if valid season is specified @@ -206,3 +205,46 @@ func GetSeasonalAnime(token, year, season, sort string, limit, offset int, field return seasonalAnime, nil } + +// get anime suggestions for the user +func GetSuggestedAnime(token string, limit, offset int, fields []string) (SuggestedAnime, error){ + var suggestedAnime SuggestedAnime + + // error handling for limit and offset + limitsErr := limitsErrHandler(limit, offset) + if limitsErr != nil { + return suggestedAnime, limitsErr + } + + // handle all the errors for the fields + fields, err := fieldsErrHandler(fields) + if err != nil { + return suggestedAnime, err + } + + endpoint, _ := urlGenerator( + BASE_URL + "/suggestions", + []string{"limit", "offset", "fields"}, + [][]string{{strconv.Itoa(limit)}, {strconv.Itoa(offset)}, fields}, + true, + ) + + // gets data from API and stores it in a struct + var suggestedAnimeData SuggestedAnimeRaw + data := requestHandler(token, endpoint) + json.Unmarshal([]byte(data), &suggestedAnimeData) + + // Adding all the animes to another list to get formatted results later + var animes []Anime + for _, element := range suggestedAnimeData.Data { + animes = append(animes, element.Anime) + } + + // finally generate RecommendedAnime struct + suggestedAnime = SuggestedAnime { + Animes: animes, + Paging: suggestedAnimeData.Paging, + } + + return suggestedAnime, nil +} diff --git a/anime/suggestedanime.structs.go b/anime/suggestedanime.structs.go new file mode 100644 index 0000000..a7b3a60 --- /dev/null +++ b/anime/suggestedanime.structs.go @@ -0,0 +1,31 @@ +/* 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 SuggestedAnimeRaw struct { + Data []struct { + Anime Anime `json:"node"` + } `json:"data"` + Paging ListPaging `json:"paging"` +} + +// this is how mal2go returns data +type SuggestedAnime struct { + Animes []Anime + Paging ListPaging +} -- cgit v1.2.3