package main import ( "bytes" "encoding/json" "io" "log" "net/http" ) func AniListQuery(body interface{}, login bool) (json.RawMessage, string) { reader, _ := json.Marshal(body) response, err := http.NewRequest("POST", "https://graphql.anilist.co", bytes.NewBuffer(reader)) if err != nil { log.Printf("Failed at response, %s\n", err) } if login && (AniListJWT{}) != aniListJwt { response.Header.Add("Authorization", "Bearer "+aniListJwt.AccessToken) } else if login { return nil, "Please login to anilist to make this request" } response.Header.Add("Content-Type", "application/json") response.Header.Add("Accept", "application/json") client := &http.Client{} res, reserr := client.Do(response) if reserr != nil { log.Printf("Failed at res, %s\n", err) } defer res.Body.Close() returnedBody, err := io.ReadAll(res.Body) return returnedBody, res.Status } func (a *App) GetAniListItem(aniId int, login bool) AniListGetSingleAnime { var user = a.GetAniListLoggedInUser() var neededVariables interface{} if login { neededVariables = struct { MediaId int `json:"mediaId"` UserId int `json:"userId"` ListType string `json:"listType"` }{ MediaId: aniId, UserId: user.Data.Viewer.ID, ListType: "ANIME", } } else { neededVariables = struct { MediaId int `json:"mediaId"` ListType string `json:"listType"` }{ MediaId: aniId, ListType: "ANIME", } } body := struct { Query string `json:"query"` Variables interface{} `json:"variables"` }{ Query: ` query($userId: Int, $mediaId: Int, $listType: MediaType) { MediaList(mediaId: $mediaId, userId: $userId, type: $listType) { id mediaId userId media { id idMal title { romaji english native } description coverImage { large } season seasonYear status episodes nextAiringEpisode { airingAt timeUntilAiring episode } tags{ id name description rank isMediaSpoiler isAdult } isAdult } status startedAt{ year month day } completedAt{ year month day } notes progress score repeat user { id name avatar { large medium } statistics { anime { count statuses { status count } } } } } } `, Variables: neededVariables, } returnedBody, status := AniListQuery(body, login) var post AniListGetSingleAnime if status == "404 Not Found" && login == false { return post } if status == "404 Not Found" && login { post = a.GetAniListItem(aniId, false) } err := json.Unmarshal(returnedBody, &post) if err != nil { log.Printf("Failed at unmarshal, %s\n", err) } if login == false { post.Data.MediaList.UserID = user.Data.Viewer.ID post.Data.MediaList.Status = "" post.Data.MediaList.StartedAt.Year = 0 post.Data.MediaList.StartedAt.Month = 0 post.Data.MediaList.StartedAt.Day = 0 post.Data.MediaList.CompletedAt.Year = 0 post.Data.MediaList.CompletedAt.Month = 0 post.Data.MediaList.CompletedAt.Day = 0 post.Data.MediaList.Notes = "" post.Data.MediaList.Progress = 0 post.Data.MediaList.Score = 0 post.Data.MediaList.Repeat = 0 post.Data.MediaList.User.ID = user.Data.Viewer.ID post.Data.MediaList.User.Name = user.Data.Viewer.Name post.Data.MediaList.User.Avatar.Large = user.Data.Viewer.Avatar.Large post.Data.MediaList.User.Avatar.Medium = user.Data.Viewer.Avatar.Medium post.Data.MediaList.User.Statistics.Anime.Count = 0 // This provides an empty array and frees up the memory from the garbage collector post.Data.MediaList.User.Statistics.Anime.Statuses = nil } return post } func (a *App) AniListSearch(query string) any { type Variables struct { Search string `json:"search"` ListType string `json:"listType"` } body := struct { Query string `json:"query"` Variables Variables `json:"variables"` }{ Query: ` query ($search: String!, $listType: MediaType) { Page (page: 1, perPage: 100) { pageInfo { total currentPage lastPage hasNextPage perPage } media (search: $search, type: $listType) { id idMal title { romaji english native } description coverImage { large } season seasonYear status episodes nextAiringEpisode{ airingAt timeUntilAiring episode } tags{ id name description rank isMediaSpoiler isAdult } isAdult } } } `, Variables: Variables{ Search: query, ListType: "ANIME", }, } returnedBody, _ := AniListQuery(body, false) var post interface{} err := json.Unmarshal(returnedBody, &post) if err != nil { log.Printf("Failed at unmarshal, %s\n", err) } return post } func (a *App) GetAniListUserWatchingList(page int, perPage int, sort string) AniListCurrentUserWatchList { var user = a.GetAniListLoggedInUser() type Variables struct { Page int `json:"page"` PerPage int `json:"perPage"` UserId int `json:"userId"` ListType string `json:"listType"` Status string `json:"status"` Sort string `json:"sort"` } body := struct { Query string `json:"query"` Variables Variables `json:"variables"` }{ Query: ` query( $page: Int $perPage: Int $userId: Int $listType: MediaType $status: MediaListStatus $sort:[MediaListSort] ) { Page(page: $page, perPage: $perPage) { pageInfo { total perPage currentPage lastPage hasNextPage } mediaList(userId: $userId, type: $listType, status: $status, sort: $sort) { id mediaId userId media { id idMal title { romaji english native } description coverImage { large } season seasonYear status episodes nextAiringEpisode{ airingAt timeUntilAiring episode } tags{ id name description rank isMediaSpoiler isAdult } isAdult } status startedAt { year month day } completedAt { year month day } notes progress score repeat user { id name avatar{ large medium } statistics { anime { count statuses { status count } } } } } } } `, Variables: Variables{ Page: page, PerPage: perPage, UserId: user.Data.Viewer.ID, ListType: "ANIME", Status: "CURRENT", Sort: sort, }, } returnedBody, _ := AniListQuery(body, true) var post AniListCurrentUserWatchList err := json.Unmarshal(returnedBody, &post) if err != nil { log.Printf("Failed at unmarshal, %s\n", err) } // Getting the real total, finding the real last page and storing that in the Page info statuses := post.Data.Page.MediaList[0].User.Statistics.Anime.Statuses var total int for _, status := range statuses { if status.Status == "CURRENT" { total = status.Count } } lastPage := total / perPage post.Data.Page.PageInfo.Total = total post.Data.Page.PageInfo.LastPage = lastPage return post } func (a *App) AniListUpdateEntry(updateBody AniListUpdateVariables) AniListGetSingleAnime { body := struct { Query string `json:"query"` Variables AniListUpdateVariables `json:"variables"` }{ Query: ` mutation( $mediaId:Int, $progress:Int, $status:MediaListStatus, $score:Float, $repeat:Int, $notes:String, $startedAt:FuzzyDateInput, $completedAt:FuzzyDateInput, ){ SaveMediaListEntry( mediaId:$mediaId, progress:$progress, status:$status, score:$score, repeat:$repeat, notes:$notes, startedAt:$startedAt completedAt:$completedAt ){ id mediaId userId media { id idMal title { romaji english native } description coverImage { large } season seasonYear status episodes nextAiringEpisode { airingAt timeUntilAiring episode } isAdult } status startedAt{ year month day } completedAt{ year month day } notes progress score repeat user { id name avatar{ large medium } statistics{ anime{ count statuses{ status count } } } } } } `, Variables: updateBody, } returnedBody, _ := AniListQuery(body, true) var returnedJson AniListUpdateReturn err := json.Unmarshal(returnedBody, &returnedJson) if err != nil { log.Printf("Failed at unmarshal, %s\n", err) } var post AniListGetSingleAnime post.Data.MediaList = returnedJson.Data.SaveMediaListEntry return post } func (a *App) AniListDeleteEntry(mediaListId int) DeleteAniListReturn { type Variables = struct { Id int `json:"id"` } body := struct { Query string `json:"query"` Variables Variables `json:"variables"` }{ Query: ` mutation( $id:Int, ){ DeleteMediaListEntry( id:$id, ){ deleted } } `, Variables: Variables{ Id: mediaListId, }, } returnedBody, _ := AniListQuery(body, true) var post DeleteAniListReturn err := json.Unmarshal(returnedBody, &post) if err != nil { log.Printf("Failed at unmarshal, %s\n", err) } return post }