The AniListUpdateEntry mutation was mangled in 54c109a when the
standardized media field block was pasted in without the media { }
wrapper: media-level fields (idMal, title, ...) sat directly on
SaveMediaListEntry (which returns MediaList), the MediaList fields
(status, startedAt, ..., user) ended up at the Mutation root, and a
stray closing brace made the document a guaranteed 400. Because the
response status was discarded, the frontend received a zeroed
AniListGetSingleAnime and blanked the anime page after every submit,
making AniList appear logged out (and the change was never saved).
- Restore the mutation to match the tested bruno request: id/mediaId/
userId, standard media block inside media { }, MediaList fields
inside the selection, balanced braces.
- AniListUpdateEntry and AniListDeleteEntry now return an error on
403/non-200/unparseable responses (mirroring
GetAniListUserWatchingList) instead of silently returning zero
values.
- Anime.svelte guards against replacing the page data with an empty
response and raises the API error modal instead.
Bump productVersion to 1.6.1.
1123 lines
23 KiB
Go
1123 lines
23 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"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)
|
|
if err != nil {
|
|
return nil, "Could not read the returned body."
|
|
}
|
|
|
|
return returnedBody, res.Status
|
|
}
|
|
|
|
func (a *App) GetAniListItem(aniId int, login bool) AniListGetSingleAnime {
|
|
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 {
|
|
userPreferred
|
|
romaji
|
|
english
|
|
native
|
|
}
|
|
description
|
|
coverImage {
|
|
extraLarge
|
|
large
|
|
medium
|
|
color
|
|
}
|
|
startDate {
|
|
year
|
|
month
|
|
day
|
|
}
|
|
endDate {
|
|
year
|
|
month
|
|
day
|
|
}
|
|
bannerImage
|
|
format
|
|
season
|
|
seasonYear
|
|
status
|
|
episodes
|
|
duration
|
|
countryOfOrigin
|
|
source
|
|
synonyms
|
|
averageScore
|
|
meanScore
|
|
popularity
|
|
trending
|
|
favourites
|
|
isFavourite
|
|
relations {
|
|
nodes {
|
|
id
|
|
title {
|
|
userPreferred
|
|
romaji
|
|
english
|
|
native
|
|
}
|
|
}
|
|
}
|
|
nextAiringEpisode {
|
|
airingAt
|
|
timeUntilAiring
|
|
episode
|
|
}
|
|
airingSchedule {
|
|
nodes {
|
|
id
|
|
airingAt
|
|
timeUntilAiring
|
|
episode
|
|
mediaId
|
|
}
|
|
}
|
|
genres
|
|
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 {
|
|
return post
|
|
}
|
|
|
|
if status == "404 Not Found" {
|
|
post = a.GetAniListItem(aniId, false)
|
|
}
|
|
|
|
err := json.Unmarshal(returnedBody, &post)
|
|
if err != nil {
|
|
log.Printf("Failed at unmarshal, %s\n", err)
|
|
}
|
|
|
|
if !login {
|
|
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) (interface{}, error) {
|
|
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 {
|
|
userPreferred
|
|
romaji
|
|
english
|
|
native
|
|
}
|
|
description
|
|
coverImage {
|
|
extraLarge
|
|
large
|
|
medium
|
|
color
|
|
}
|
|
bannerImage
|
|
format
|
|
season
|
|
seasonYear
|
|
status
|
|
episodes
|
|
duration
|
|
countryOfOrigin
|
|
source
|
|
synonyms
|
|
averageScore
|
|
meanScore
|
|
popularity
|
|
trending
|
|
favourites
|
|
isFavourite
|
|
relations{
|
|
nodes{
|
|
id
|
|
title{
|
|
userPreferred
|
|
romaji
|
|
english
|
|
native
|
|
}
|
|
}
|
|
}
|
|
startDate{
|
|
year
|
|
month
|
|
day
|
|
}
|
|
endDate{
|
|
year
|
|
month
|
|
day
|
|
}
|
|
nextAiringEpisode{
|
|
airingAt
|
|
timeUntilAiring
|
|
episode
|
|
}
|
|
airingSchedule{
|
|
nodes{
|
|
id
|
|
airingAt
|
|
timeUntilAiring
|
|
episode
|
|
mediaId
|
|
}
|
|
}
|
|
genres
|
|
tags{
|
|
id
|
|
name
|
|
description
|
|
rank
|
|
isMediaSpoiler
|
|
isAdult
|
|
}
|
|
isAdult
|
|
}
|
|
|
|
}
|
|
}
|
|
`,
|
|
Variables: Variables{
|
|
Search: query,
|
|
ListType: "ANIME",
|
|
},
|
|
}
|
|
returnedBody, status := AniListQuery(body, false)
|
|
if status != "200 OK" {
|
|
return nil, fmt.Errorf("API search failed with status: %s", status)
|
|
}
|
|
var post interface{}
|
|
err := json.Unmarshal(returnedBody, &post)
|
|
if err != nil {
|
|
log.Printf("Failed at unmarshal, %s\n", err)
|
|
return nil, fmt.Errorf("Failed to parse search results")
|
|
}
|
|
return post, nil
|
|
}
|
|
|
|
func (a *App) GetAniListUserWatchingList(page int, perPage int, sort string) (AniListCurrentUserWatchList, error) {
|
|
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 {
|
|
userPreferred
|
|
romaji
|
|
english
|
|
native
|
|
}
|
|
description
|
|
coverImage {
|
|
extraLarge
|
|
large
|
|
medium
|
|
color
|
|
}
|
|
bannerImage
|
|
format
|
|
season
|
|
seasonYear
|
|
status
|
|
episodes
|
|
duration
|
|
countryOfOrigin
|
|
source
|
|
synonyms
|
|
averageScore
|
|
meanScore
|
|
popularity
|
|
trending
|
|
favourites
|
|
isFavourite
|
|
relations{
|
|
nodes{
|
|
id
|
|
title{
|
|
userPreferred
|
|
romaji
|
|
english
|
|
native
|
|
}
|
|
}
|
|
}
|
|
startDate{
|
|
year
|
|
month
|
|
day
|
|
}
|
|
endDate{
|
|
year
|
|
month
|
|
day
|
|
}
|
|
nextAiringEpisode{
|
|
airingAt
|
|
timeUntilAiring
|
|
episode
|
|
}
|
|
airingSchedule{
|
|
nodes{
|
|
id
|
|
airingAt
|
|
timeUntilAiring
|
|
episode
|
|
mediaId
|
|
}
|
|
}
|
|
genres
|
|
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, status := AniListQuery(body, true)
|
|
|
|
var badPost struct {
|
|
Errors []struct {
|
|
Message string `json:"message"`
|
|
Status int `json:"status"`
|
|
Locations []struct {
|
|
Line int `json:"line"`
|
|
Column int `json:"column"`
|
|
} `json:"locations"`
|
|
} `json:"errors"`
|
|
Data any `json:"data"`
|
|
}
|
|
var post AniListCurrentUserWatchList
|
|
if status == "200 OK" {
|
|
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
|
|
}
|
|
|
|
if status == "403 Forbidden" {
|
|
err := json.Unmarshal(returnedBody, &badPost)
|
|
if err != nil {
|
|
log.Printf("Failed at unmarshal, %s\n", err)
|
|
return post, fmt.Errorf("API authentication error")
|
|
}
|
|
return post, fmt.Errorf("AniList API error: %s", badPost.Errors[0].Message)
|
|
}
|
|
if status != "200 OK" {
|
|
return post, fmt.Errorf("API request failed with status: %s", status)
|
|
}
|
|
|
|
return post, nil
|
|
}
|
|
|
|
func (a *App) AniListUpdateEntry(updateBody AniListUpdateVariables) (AniListGetSingleAnime, error) {
|
|
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 {
|
|
userPreferred
|
|
romaji
|
|
english
|
|
native
|
|
}
|
|
description
|
|
coverImage {
|
|
extraLarge
|
|
large
|
|
medium
|
|
color
|
|
}
|
|
bannerImage
|
|
format
|
|
season
|
|
seasonYear
|
|
status
|
|
episodes
|
|
duration
|
|
countryOfOrigin
|
|
source
|
|
synonyms
|
|
averageScore
|
|
meanScore
|
|
popularity
|
|
trending
|
|
favourites
|
|
isFavourite
|
|
relations {
|
|
nodes {
|
|
id
|
|
title {
|
|
userPreferred
|
|
romaji
|
|
english
|
|
native
|
|
}
|
|
}
|
|
}
|
|
startDate {
|
|
year
|
|
month
|
|
day
|
|
}
|
|
endDate {
|
|
year
|
|
month
|
|
day
|
|
}
|
|
nextAiringEpisode {
|
|
airingAt
|
|
timeUntilAiring
|
|
episode
|
|
}
|
|
airingSchedule {
|
|
nodes {
|
|
id
|
|
airingAt
|
|
timeUntilAiring
|
|
episode
|
|
mediaId
|
|
}
|
|
}
|
|
genres
|
|
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: updateBody,
|
|
}
|
|
|
|
returnedBody, status := AniListQuery(body, true)
|
|
|
|
var badPost struct {
|
|
Errors []struct {
|
|
Message string `json:"message"`
|
|
Status int `json:"status"`
|
|
Locations []struct {
|
|
Line int `json:"line"`
|
|
Column int `json:"column"`
|
|
} `json:"locations"`
|
|
} `json:"errors"`
|
|
Data any `json:"data"`
|
|
}
|
|
|
|
if status == "403 Forbidden" {
|
|
err := json.Unmarshal(returnedBody, &badPost)
|
|
if err != nil {
|
|
log.Printf("Failed at unmarshal, %s\n", err)
|
|
return AniListGetSingleAnime{}, fmt.Errorf("API authentication error")
|
|
}
|
|
return AniListGetSingleAnime{}, fmt.Errorf("AniList API error: %s", badPost.Errors[0].Message)
|
|
}
|
|
if status != "200 OK" {
|
|
return AniListGetSingleAnime{}, fmt.Errorf("API request failed with status: %s", status)
|
|
}
|
|
|
|
var returnedJson AniListUpdateReturn
|
|
err := json.Unmarshal(returnedBody, &returnedJson)
|
|
if err != nil {
|
|
log.Printf("Failed at unmarshal, %s\n", err)
|
|
return AniListGetSingleAnime{}, fmt.Errorf("failed to parse AniList update response")
|
|
}
|
|
|
|
if returnedJson.Data.SaveMediaListEntry.MediaID == 0 {
|
|
return AniListGetSingleAnime{}, fmt.Errorf("AniList returned no saved entry")
|
|
}
|
|
|
|
var post AniListGetSingleAnime
|
|
|
|
post.Data.MediaList = returnedJson.Data.SaveMediaListEntry
|
|
|
|
return post, nil
|
|
}
|
|
|
|
func (a *App) AniListDeleteEntry(mediaListId int) (DeleteAniListReturn, error) {
|
|
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, status := AniListQuery(body, true)
|
|
|
|
var badPost struct {
|
|
Errors []struct {
|
|
Message string `json:"message"`
|
|
Status int `json:"status"`
|
|
Locations []struct {
|
|
Line int `json:"line"`
|
|
Column int `json:"column"`
|
|
} `json:"locations"`
|
|
} `json:"errors"`
|
|
Data any `json:"data"`
|
|
}
|
|
|
|
if status == "403 Forbidden" {
|
|
err := json.Unmarshal(returnedBody, &badPost)
|
|
if err != nil {
|
|
log.Printf("Failed at unmarshal, %s\n", err)
|
|
return DeleteAniListReturn{}, fmt.Errorf("API authentication error")
|
|
}
|
|
return DeleteAniListReturn{}, fmt.Errorf("AniList API error: %s", badPost.Errors[0].Message)
|
|
}
|
|
if status != "200 OK" {
|
|
return DeleteAniListReturn{}, fmt.Errorf("API request failed with status: %s", status)
|
|
}
|
|
|
|
var post DeleteAniListReturn
|
|
err := json.Unmarshal(returnedBody, &post)
|
|
if err != nil {
|
|
log.Printf("Failed at unmarshal, %s\n", err)
|
|
return DeleteAniListReturn{}, fmt.Errorf("failed to parse AniList delete response")
|
|
}
|
|
|
|
return post, nil
|
|
}
|
|
|
|
func (a *App) AniListBrowse(
|
|
page int,
|
|
perPage int,
|
|
id int,
|
|
isAdult bool,
|
|
search string,
|
|
format []string,
|
|
status string,
|
|
countryOfOrigin string,
|
|
source string,
|
|
season string,
|
|
seasonYear int,
|
|
year string,
|
|
onList bool,
|
|
yearLesser int,
|
|
yearGreater int,
|
|
episodeLesser int,
|
|
episodeGreater int,
|
|
durationLesser int,
|
|
durationGreater int,
|
|
chapterLesser int,
|
|
chapterGreater int,
|
|
volumeLesser int,
|
|
volumeGreater int,
|
|
licensedBy []int,
|
|
isLicensed bool,
|
|
genres []string,
|
|
excludedGenres []string,
|
|
tags []string,
|
|
excludedTags []string,
|
|
minimumTagRank int,
|
|
sort []string) (AniListCurrentUserWatchList, error) {
|
|
// user := a.GetAniListLoggedInUser()
|
|
type Variables struct {
|
|
Page int `json:"page"`
|
|
PerPage int `json:"perPage"`
|
|
Id int `json:"id"`
|
|
IsAdult bool `json:"isAdult"`
|
|
Search string `json:"search"`
|
|
Format []string `json:"format"`
|
|
Status string `json:"status"`
|
|
CountryOfOrigin string `json:"countryOfOrigin"`
|
|
Source string `json:"source"`
|
|
Season string `json:"season"`
|
|
SeasonYear int `json:"seasonYear"`
|
|
Year string `json:"year"`
|
|
OnList bool `json:"onList"`
|
|
YearLesser int `json:"yearLesser"`
|
|
YearGreater int `json:"yearGreater"`
|
|
EpisodeLesser int `json:"episodeLesser"`
|
|
EpisodeGreater int `json:"episodeGreater"`
|
|
DurationLesser int `json:"durationLesser"`
|
|
DurationGreater int `json:"durationGreater"`
|
|
ChapterLesser int `json:"chapterLesser"`
|
|
ChapterGreater int `json:"chapterGreater"`
|
|
VolumeLesser int `json:"volumeLesser"`
|
|
VolumeGreater int `json:"volumeGreater"`
|
|
LicensedBy []int `json:"licensedBy"`
|
|
IsLicensed bool `json:"isLicensed"`
|
|
Genres []string `json:"genres"`
|
|
ExcludedGenres []string `json:"excludedGenres"`
|
|
Tags []string `json:"tags"`
|
|
ExcludedTags []string `json:"excludedTags"`
|
|
MinimumTagRank int `json:"minimumTagRank"`
|
|
Sort []string `json:"sort"`
|
|
}
|
|
body := struct {
|
|
Query string `json:"query"`
|
|
Variables Variables `json:"variables"`
|
|
}{
|
|
Query: `
|
|
query (
|
|
$page: Int = 1
|
|
$perPage: Int = 20
|
|
$id: Int
|
|
$isAdult: Boolean = false
|
|
$search: String
|
|
$format: [MediaFormat]
|
|
$status: MediaStatus
|
|
$countryOfOrigin: CountryCode
|
|
$source: MediaSource
|
|
$season: MediaSeason
|
|
$seasonYear: Int
|
|
$year: String
|
|
$onList: Boolean
|
|
$yearLesser: FuzzyDateInt
|
|
$yearGreater: FuzzyDateInt
|
|
$episodeLesser: Int
|
|
$episodeGreater: Int
|
|
$durationLesser: Int
|
|
$durationGreater: Int
|
|
$chapterLesser: Int
|
|
$chapterGreater: Int
|
|
$volumeLesser: Int
|
|
$volumeGreater: Int
|
|
$licensedBy: [Int]
|
|
$isLicensed: Boolean
|
|
$genres: [String]
|
|
$excludedGenres: [String]
|
|
$tags: [String]
|
|
$excludedTags: [String]
|
|
$minimumTagRank: Int
|
|
$sort: [MediaSort] = [POPULARITY_DESC, SCORE_DESC]
|
|
) {
|
|
Page(page: $page, perPage: $perPage) {
|
|
pageInfo {
|
|
total
|
|
perPage
|
|
currentPage
|
|
lastPage
|
|
hasNextPage
|
|
}
|
|
media(
|
|
id: $id
|
|
type: ANIME
|
|
season: $season
|
|
format_in: $format
|
|
status: $status
|
|
countryOfOrigin: $countryOfOrigin
|
|
source: $source
|
|
search: $search
|
|
onList: $onList
|
|
seasonYear: $seasonYear
|
|
startDate_like: $year
|
|
startDate_lesser: $yearLesser
|
|
startDate_greater: $yearGreater
|
|
episodes_lesser: $episodeLesser
|
|
episodes_greater: $episodeGreater
|
|
duration_lesser: $durationLesser
|
|
duration_greater: $durationGreater
|
|
chapters_lesser: $chapterLesser
|
|
chapters_greater: $chapterGreater
|
|
volumes_lesser: $volumeLesser
|
|
volumes_greater: $volumeGreater
|
|
licensedById_in: $licensedBy
|
|
isLicensed: $isLicensed
|
|
genre_in: $genres
|
|
genre_not_in: $excludedGenres
|
|
tag_in: $tags
|
|
tag_not_in: $excludedTags
|
|
minimumTagRank: $minimumTagRank
|
|
sort: $sort
|
|
isAdult: $isAdult
|
|
) {
|
|
id
|
|
mediaId
|
|
userId
|
|
media {
|
|
id
|
|
idMal
|
|
title {
|
|
userPreferred
|
|
romaji
|
|
english
|
|
native
|
|
}
|
|
description
|
|
coverImage {
|
|
extraLarge
|
|
large
|
|
medium
|
|
color
|
|
}
|
|
bannerImage
|
|
format
|
|
season
|
|
seasonYear
|
|
status
|
|
episodes
|
|
duration
|
|
countryOfOrigin
|
|
source
|
|
synonyms
|
|
averageScore
|
|
meanScore
|
|
popularity
|
|
trending
|
|
favourites
|
|
isFavourite
|
|
relations {
|
|
nodes {
|
|
id
|
|
title {
|
|
userPreferred
|
|
romaji
|
|
english
|
|
native
|
|
}
|
|
}
|
|
}
|
|
startDate {
|
|
year
|
|
month
|
|
day
|
|
}
|
|
endDate {
|
|
year
|
|
month
|
|
day
|
|
}
|
|
nextAiringEpisode {
|
|
airingAt
|
|
timeUntilAiring
|
|
episode
|
|
}
|
|
airingSchedule {
|
|
nodes {
|
|
id
|
|
airingAt
|
|
timeUntilAiring
|
|
episode
|
|
mediaId
|
|
}
|
|
}
|
|
genres
|
|
tags {
|
|
id
|
|
name
|
|
description
|
|
rank
|
|
isMediaSpoiler
|
|
isAdult
|
|
}
|
|
isAdult
|
|
}
|
|
|
|
}
|
|
}
|
|
`,
|
|
Variables: Variables{
|
|
Page: page,
|
|
PerPage: perPage,
|
|
Id: id,
|
|
IsAdult: isAdult,
|
|
Search: search,
|
|
Format: format,
|
|
Status: status,
|
|
CountryOfOrigin: countryOfOrigin,
|
|
Source: source,
|
|
Season: season,
|
|
SeasonYear: seasonYear,
|
|
Year: year,
|
|
OnList: onList,
|
|
YearLesser: yearLesser,
|
|
YearGreater: yearGreater,
|
|
EpisodeLesser: episodeLesser,
|
|
EpisodeGreater: episodeGreater,
|
|
DurationLesser: durationLesser,
|
|
DurationGreater: durationGreater,
|
|
ChapterLesser: chapterLesser,
|
|
ChapterGreater: chapterGreater,
|
|
VolumeLesser: volumeLesser,
|
|
VolumeGreater: volumeGreater,
|
|
LicensedBy: licensedBy,
|
|
IsLicensed: isLicensed,
|
|
Genres: genres,
|
|
ExcludedGenres: excludedGenres,
|
|
Tags: tags,
|
|
ExcludedTags: excludedTags,
|
|
MinimumTagRank: minimumTagRank,
|
|
Sort: sort,
|
|
},
|
|
}
|
|
|
|
returnedBody, status := AniListQuery(body, true)
|
|
|
|
var badPost struct {
|
|
Errors []struct {
|
|
Message string `json:"message"`
|
|
Status int `json:"status"`
|
|
Locations []struct {
|
|
Line int `json:"line"`
|
|
Column int `json:"column"`
|
|
} `json:"locations"`
|
|
} `json:"errors"`
|
|
Data any `json:"data"`
|
|
}
|
|
var post AniListCurrentUserWatchList
|
|
if status == "200 OK" {
|
|
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
|
|
}
|
|
|
|
if status == "403 Forbidden" {
|
|
err := json.Unmarshal(returnedBody, &badPost)
|
|
if err != nil {
|
|
log.Printf("Failed at unmarshal, %s\n", err)
|
|
return post, fmt.Errorf("API authentication error")
|
|
}
|
|
return post, fmt.Errorf("AniList API error: %s", badPost.Errors[0].Message)
|
|
}
|
|
if status != "200 OK" {
|
|
return post, fmt.Errorf("API request failed with status: %s", status)
|
|
}
|
|
|
|
return post, nil
|
|
}
|