Add the `genres` field to AniList GraphQL queries and type definitions: - Add genres field to GetAniListItem query for fetching single anime details - Add genres field to AniListSearch query for search results - Add genres field to GetAniListUserWatchingList query for user's watch list - Update MediaList type definition to include Genres []string field This enhancement allows the application to retrieve and display anime genre information from the AniList API, providing users with better categorization and discovery capabilities.
554 lines
10 KiB
Go
554 lines
10 KiB
Go
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)
|
|
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 {
|
|
romaji
|
|
english
|
|
native
|
|
}
|
|
description
|
|
coverImage {
|
|
large
|
|
}
|
|
season
|
|
seasonYear
|
|
status
|
|
episodes
|
|
nextAiringEpisode {
|
|
airingAt
|
|
timeUntilAiring
|
|
episode
|
|
}
|
|
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) 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
|
|
}
|
|
genres
|
|
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 {
|
|
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
|
|
}
|
|
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)
|
|
}
|
|
log.Fatal(badPost.Errors[0].Message)
|
|
}
|
|
|
|
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
|
|
}
|