initial commit

This commit is contained in:
Tordarus 2022-02-03 17:23:29 +01:00
commit c8669da106
12 changed files with 1014 additions and 0 deletions

53
airingschedule.go Normal file
View File

@ -0,0 +1,53 @@
package anilist
func (api *Api) GetAiringSchedule(vars AiringScheduleQuery, onError func(error)) <-chan *AiringSchedule {
resp := responseObj[*page[AiringSchedule]]{}
return requestPaged(api, getAiringScheduleQuery, vars.toMap(), &resp, onError)
}
const (
getAiringScheduleQuery = `query (
$id: Int,
$mediaId: Int,
$episode: Int,
$airingAt: Int,
$notYetAired: Boolean,
$id_in: [Int],
$id_not_in: [Int],
$mediaId_in: [Int],
$mediaId_not_in: [Int],
$episode_in: [Int],
$episode_not_in: [Int],
$episode_greater: Int,
$episode_lesser: Int,
$airingAt_greater: Int,
$airingAt_lesser: Int,
$sort: [AiringSort],
$page: Int
)
{
Page (page: $page) {
pageInfo ` + subSelectionPageInfo + `
airingSchedules (
id: $id,
mediaId: $mediaId,
episode: $episode,
airingAt: $airingAt,
notYetAired: $notYetAired,
id_in: $id_in,
id_not_in: $id_not_in,
mediaId_in: $mediaId_in,
mediaId_not_in: $mediaId_not_in,
episode_in: $episode_in,
episode_not_in: $episode_not_in,
episode_greater: $episode_greater,
episode_lesser: $episode_lesser,
airingAt_greater: $airingAt_greater,
airingAt_lesser: $airingAt_lesser,
sort: $sort,
) ` + subSelectionAiringSchedule + `
}
}`
)

100
api.go Normal file
View File

@ -0,0 +1,100 @@
package anilist
import (
"bytes"
"encoding/json"
"net/http"
)
type Api struct {
AccessToken string
}
func NewApi(accessToken string) *Api {
return &Api{AccessToken: accessToken}
}
type queryObj struct {
Query string `json:"query"`
Vars interface{} `json:"variables"`
}
type responseObj[T any] struct {
Data T `json:"data"`
}
func request[T any](api *Api, query string, vars map[string]interface{}, respObj *responseObj[T]) error {
q := &queryObj{
Query: query,
Vars: vars,
}
queryData, err := json.Marshal(q)
if err != nil {
return err
}
req, err := http.NewRequest("POST", "https://graphql.anilist.co/", bytes.NewReader(queryData))
if err != nil {
return err
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Authorization", "Bearer "+api.AccessToken)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
//data, _ := ioutil.ReadAll(resp.Body)
//fmt.Println(string(data))
dec := json.NewDecoder(resp.Body)
err = dec.Decode(respObj)
if err != nil {
return err
}
return nil
}
func requestPaged[R any](api *Api, query string, vars map[string]interface{}, respObj *responseObj[*page[R]], onError func(error)) <-chan *R {
resp := responseObj[struct {
Page *page[R] `json:"Page"`
}]{}
out := make(chan *R, 50)
go func() {
defer close(out)
vars["page"] = 0
for {
if p, ok := vars["page"].(int); ok {
vars["page"] = p + 1
}
err := request(api, query, vars, &resp)
if err != nil {
if onError != nil {
onError(err)
}
return
}
for _, value := range resp.Data.Page.Data() {
value := value
out <- &value
}
if resp.Data.Page.PageInfo.CurrentPage == resp.Data.Page.PageInfo.LastPage {
return
}
}
}()
return out
}

3
go.mod Normal file
View File

@ -0,0 +1,3 @@
module git.tordarus.net/Tordarus/anilist
go 1.18

0
go.sum Normal file
View File

98
media.go Normal file
View File

@ -0,0 +1,98 @@
package anilist
func (api *Api) GetMedia(vars MediaQuery, onError func(error)) <-chan *Media {
resp := responseObj[*page[Media]]{}
return requestPaged(api, getMediaQuery, vars.toMap(), &resp, onError)
}
const (
getMediaQuery = `query (
$id: Int,
$startDate: FuzzyDateInt,
$endDate: FuzzyDateInt,
$season: MediaSeason,
$seasonYear: Int,
$type: MediaType,
$format: MediaFormat,
$status: MediaStatus,
$episodes: Int,
$duration: Int,
$chapters: Int,
$volumes: Int,
$isAdult: Boolean,
$genre: String,
$tag: String,
$source: MediaSource,
$search: String,
$id_in: [Int],
$id_not_in: [Int],
$startDate_greater: FuzzyDateInt,
$startDate_lesser: FuzzyDateInt,
$endDate_greater: FuzzyDateInt,
$endDate_lesser: FuzzyDateInt,
$format_in: [MediaFormat],
$format_not_in: [MediaFormat],
$status_in: [MediaStatus],
$status_not_in: [MediaStatus],
$episodes_greater: Int,
$episodes_lesser: Int,
$duration_greater: Int,
$duration_lesser: Int,
$volumes_greater: Int,
$volumes_lesser: Int,
$genre_in: [String],
$genre_not_in: [String],
$tag_in: [String],
$tag_not_in: [String],
$source_in: [MediaSource],
$sort:[MediaSort],
$page: Int
)
{
Page (page: $page) {
pageInfo ` + subSelectionPageInfo + `
media (
id: $id,
startDate: $startDate,
endDate: $endDate,
season: $season,
seasonYear: $seasonYear,
type: $type,
format: $format,
status: $status,
episodes: $episodes,
duration: $duration,
chapters: $chapters,
volumes: $volumes,
isAdult: $isAdult,
genre: $genre,
tag: $tag,
source: $source,
search: $search,
id_in: $id_in,
id_not_in: $id_not_in,
startDate_greater: $startDate_greater,
startDate_lesser: $startDate_lesser,
endDate_greater: $endDate_greater,
endDate_lesser: $endDate_lesser,
format_in: $format_in,
format_not_in: $format_not_in,
status_in: $status_in,
status_not_in: $status_not_in,
episodes_greater: $episodes_greater,
episodes_lesser: $episodes_lesser,
duration_greater: $duration_greater,
duration_lesser: $duration_lesser,
volumes_greater: $volumes_greater,
volumes_lesser: $volumes_lesser,
genre_in: $genre_in,
genre_not_in: $genre_not_in,
tag_in: $tag_in,
tag_not_in: $tag_not_in,
source_in: $source_in,
sort: $sort
) ` + subSelectionMedia + `
}
}`
)

51
medialist.go Normal file
View File

@ -0,0 +1,51 @@
package anilist
func (api *Api) GetMediaList(vars MediaListQuery, onError func(error)) <-chan *MediaList {
resp := responseObj[*page[MediaList]]{}
return requestPaged(api, getMediaListQuery, vars.toMap(), &resp, onError)
}
const (
getMediaListQuery = `query (
$id: Int,
$userId: Int,
$userName: String,
$type: MediaType,
$status: MediaListStatus,
$mediaId: Int,
$isFollowing: Boolean,
$notes: String,
$userId_in: [Int],
$status_in: [MediaListStatus],
$status_not_in: [MediaListStatus],
$status_not: MediaListStatus,
$mediaId_in: [Int],
$mediaId_not_in: [Int],
$sort: [MediaListSort],
$page: Int
)
{
Page (page: $page) {
pageInfo ` + subSelectionPageInfo + `
mediaList (
id: $id,
userId: $userId,
userName: $userName,
type: $type,
status: $status,
mediaId: $mediaId,
isFollowing: $isFollowing,
notes: $notes,
userId_in: $userId_in,
status_in: $status_in,
status_not_in: $status_not_in,
status_not: $status_not,
mediaId_in: $mediaId_in,
mediaId_not_in: $mediaId_not_in,
sort: $sort
) ` + subSelectionMediaList + `
}
}`
)

45
page.go Normal file
View File

@ -0,0 +1,45 @@
package anilist
import (
"reflect"
)
type page[T any] struct {
PageInfo *pageInfo `json:"pageInfo"`
Media []Media `json:"media"`
MediaList []MediaList `json:"mediaList"`
Users []User `json:"users"`
AiringSchedules []AiringSchedule `json:"airingSchedules"`
}
func (p *page[T]) Data() []T {
if reflect.TypeOf(new(T)).Elem() == reflect.TypeOf(new(User)).Elem() {
var data interface{} = p.Users
return data.([]T)
}
if reflect.TypeOf(new(T)).Elem() == reflect.TypeOf(new(Media)).Elem() {
var data interface{} = p.Media
return data.([]T)
}
if reflect.TypeOf(new(T)).Elem() == reflect.TypeOf(new(MediaList)).Elem() {
var data interface{} = p.MediaList
return data.([]T)
}
if reflect.TypeOf(new(T)).Elem() == reflect.TypeOf(new(AiringSchedule)).Elem() {
var data interface{} = p.AiringSchedules
return data.([]T)
}
panic("generic type not implemented: " + reflect.TypeOf(new(T)).Elem().Name())
}
type pageInfo struct {
Total int `json:"total"`
CurrentPage int `json:"currentPage"`
LastPage int `json:"lastPage"`
HasNextPage bool `json:"hasNextPage"`
PerPage int `json:"perPage"`
}

257
queries.go Normal file
View File

@ -0,0 +1,257 @@
package anilist
import "time"
type MediaListQuery struct {
ID int
UserID int
UserName string
Type MediaType
Status MediaListStatus
MediaID int
Following bool
Notes string
UserIdIn []int
StatusIn []MediaListStatus
StatusNotIn []MediaListStatus
StatusNot MediaListStatus
MediaIdIn []int
MediaIdNotIn []int
Sort []MediaListSort
}
func (q *MediaListQuery) toMap() map[string]interface{} {
m := map[string]interface{}{}
addValue2InterfaceMap(m, "id", q.ID)
addValue2InterfaceMap(m, "userId", q.UserID)
addValue2InterfaceMap(m, "userName", q.UserName)
addValue2InterfaceMap(m, "type", q.Type)
addValue2InterfaceMap(m, "status", q.Status)
addValue2InterfaceMap(m, "mediaId", q.MediaID)
addValue2InterfaceMap(m, "isFollowing", q.Following)
addValue2InterfaceMap(m, "notes", q.Notes)
addSlice2InterfaceMap(m, "userId_in", q.UserIdIn)
addSlice2InterfaceMap(m, "status_in", q.StatusIn)
addSlice2InterfaceMap(m, "status_not_in", q.StatusNotIn)
addValue2InterfaceMap(m, "status_not", q.StatusNot)
addSlice2InterfaceMap(m, "mediaId_in", q.MediaIdIn)
addSlice2InterfaceMap(m, "mediaId_not_in", q.MediaIdNotIn)
addSlice2InterfaceMap(m, "sort", q.Sort)
return m
}
type MediaListSort string
const (
MediaListSortMediaId MediaListSort = "MEDIA_ID"
MediaListSortMediaIdDesc MediaListSort = "MEDIA_ID_DESC"
MediaListSortScore MediaListSort = "SCORE"
MediaListSortScoreDesc MediaListSort = "SCORE_DESC"
MediaListSortStatus MediaListSort = "STATUS"
MediaListSortStatusDesc MediaListSort = "STATUS_DESC"
MediaListSortProgress MediaListSort = "PROGRESS"
MediaListSortProgressDesc MediaListSort = "PROGRESS_DESC"
MediaListSortProgressVolumes MediaListSort = "PROGRESS_VOLUMES"
MediaListSortProgressVolumesDesc MediaListSort = "PROGRESS_VOLUMES_DESC"
MediaListSortRepeat MediaListSort = "REPEAT"
MediaListSortRepeatDesc MediaListSort = "REPEAT_DESC"
MediaListSortPriority MediaListSort = "PRIORITY"
MediaListSortPriorityDesc MediaListSort = "PRIORITY_DESC"
MediaListSortStartedOn MediaListSort = "STARTED_ON"
MediaListSortStartedOnDesc MediaListSort = "STARTED_ON_DESC"
MediaListSortFinishedOn MediaListSort = "FINISHED_ON"
MediaListSortFinishedOnDesc MediaListSort = "FINISHED_ON_DESC"
MediaListSortAddedTime MediaListSort = "ADDED_TIME"
MediaListSortAddedTimeDesc MediaListSort = "ADDED_TIME_DESC"
MediaListSortUpdatedTime MediaListSort = "UPDATED_TIME"
MediaListSortUpdatedTimeDesc MediaListSort = "UPDATED_TIME_DESC"
MediaListSortMediaTitleRomaji MediaListSort = "MEDIA_TITLE_ROMAJI"
MediaListSortMediaTitleRomajiDesc MediaListSort = "MEDIA_TITLE_ROMAJI_DESC"
MediaListSortMediaTitleEnglish MediaListSort = "MEDIA_TITLE_ENGLISH"
MediaListSortMediaTitleEnglishDesc MediaListSort = "MEDIA_TITLE_ENGLISH_DESC"
MediaListSortMediaTitleNative MediaListSort = "MEDIA_TITLE_NATIVE"
MediaListSortMediaTitleNativeDesc MediaListSort = "MEDIA_TITLE_NATIVE_DESC"
MediaListSortMediaPopularity MediaListSort = "MEDIA_POPULARITY"
MediaListSortMediaPopularityDesc MediaListSort = "MEDIA_POPULARITY_DESC"
)
type AiringScheduleQuery struct {
ID int
MediaID int
Episode int
AiringAt time.Time
NotYetAired bool
IdIn []int
IdNotIn []int
MediaIdIn []int
MediaIdNotIn []int
EpisodeIn []int
EpisodeNotIn []int
EpisodeGreater int
EpisodeLesser int
AiringAtGreater time.Time
AiringAtLesser time.Time
Sort []AiringSort
}
func (q *AiringScheduleQuery) toMap() map[string]interface{} {
m := map[string]interface{}{}
addValue2InterfaceMap(m, "id", q.ID)
addValue2InterfaceMap(m, "mediaId", q.MediaID)
addValue2InterfaceMap(m, "episode", q.Episode)
addValue2InterfaceMap(m, "airingAt", q.AiringAt)
addValue2InterfaceMap(m, "notYetAired", q.NotYetAired)
addSlice2InterfaceMap(m, "id_in", q.IdIn)
addSlice2InterfaceMap(m, "id_not_in", q.IdNotIn)
addSlice2InterfaceMap(m, "mediaId_in", q.MediaIdIn)
addSlice2InterfaceMap(m, "mediaId_not_in", q.MediaIdNotIn)
addSlice2InterfaceMap(m, "episode_in", q.EpisodeIn)
addSlice2InterfaceMap(m, "episode_not_in", q.EpisodeNotIn)
addValue2InterfaceMap(m, "episode_greater", q.EpisodeGreater)
addValue2InterfaceMap(m, "episode_lesser", q.EpisodeLesser)
addValue2InterfaceMap(m, "airingAt_greater", q.AiringAtGreater)
addValue2InterfaceMap(m, "airingAt_lesser", q.AiringAtLesser)
addSlice2InterfaceMap(m, "sort", q.Sort)
return m
}
type AiringSort string
const (
AiringSortId AiringSort = "ID"
AiringSortIdDesc AiringSort = "ID_DESC"
AiringSortMediaId AiringSort = "MEDIA_ID"
AiringSortMediaIdDesc AiringSort = "MEDIA_ID_DESC"
AiringSortTime AiringSort = "TIME"
AiringSortTimeDesc AiringSort = "TIME_DESC"
AiringSortEpisode AiringSort = "EPISODE"
AiringSortEpisodeDesc AiringSort = "EPISODE_DESC"
)
type MediaQuery struct {
ID int
StartDate time.Time
EndDate time.Time
Season MediaSeason
SeasonYear int
Type MediaType
Format MediaFormat
Status MediaStatus
Episodes int
Duration time.Duration
Chapters int
Volumes int
IsAdult bool
Genre string
Tag string
Source MediaSource
Search string
IdIn []int
IdNotIn []int
StartDateGreater time.Time
StartDateLesser time.Time
EndDateGreater time.Time
EndDateLesser time.Time
FormatIn []MediaFormat
FormatNotIn []MediaFormat
StatusIn []MediaStatus
StatusNotIn []MediaStatus
EpisodesGreater int
EpisodesLesser int
DurationGreater time.Duration
DurationLesser time.Duration
VolumesGreater int
VolumesLesser int
GenreIn []string
GenreNotIn []string
TagIn []string
TagNotIn []string
SourceIn []MediaSource
Sort []MediaSort
}
func (q *MediaQuery) toMap() map[string]interface{} {
m := map[string]interface{}{}
addValue2InterfaceMap(m, "id", q.ID)
addValue2InterfaceMap(m, "startDate", q.StartDate)
addValue2InterfaceMap(m, "endDate", q.EndDate)
addValue2InterfaceMap(m, "season", q.Season)
addValue2InterfaceMap(m, "seasonYear", q.SeasonYear)
addValue2InterfaceMap(m, "type", q.Type)
addValue2InterfaceMap(m, "format", q.Format)
addValue2InterfaceMap(m, "status", q.Status)
addValue2InterfaceMap(m, "episodes", q.Episodes)
addValue2InterfaceMap(m, "duration", q.Duration.Minutes())
addValue2InterfaceMap(m, "chapters", q.Chapters)
addValue2InterfaceMap(m, "volumes", q.Volumes)
addValue2InterfaceMap(m, "isAdult", q.IsAdult)
addValue2InterfaceMap(m, "genre", q.Genre)
addValue2InterfaceMap(m, "tag", q.Tag)
addValue2InterfaceMap(m, "source", q.Source)
addValue2InterfaceMap(m, "search", q.Search)
addSlice2InterfaceMap(m, "id_in", q.IdIn)
addSlice2InterfaceMap(m, "id_not_in", q.IdNotIn)
addValue2InterfaceMap(m, "startDateGreater", q.StartDateGreater)
addValue2InterfaceMap(m, "startDateLesser", q.StartDateLesser)
addValue2InterfaceMap(m, "endDateGreater", q.EndDateGreater)
addValue2InterfaceMap(m, "endDateLesser", q.EndDateLesser)
addSlice2InterfaceMap(m, "format_in", q.FormatIn)
addSlice2InterfaceMap(m, "format_not_in", q.FormatNotIn)
addSlice2InterfaceMap(m, "status_in", q.StatusIn)
addSlice2InterfaceMap(m, "status_not_in", q.StatusNotIn)
addValue2InterfaceMap(m, "episodesGreater", q.EpisodesGreater)
addValue2InterfaceMap(m, "episodesLesser", q.EpisodesLesser)
addValue2InterfaceMap(m, "durationGreater", q.DurationGreater.Minutes())
addValue2InterfaceMap(m, "durationLesser", q.DurationLesser.Minutes())
addValue2InterfaceMap(m, "volumesGreater", q.VolumesGreater)
addValue2InterfaceMap(m, "volumesLesser", q.VolumesLesser)
addSlice2InterfaceMap(m, "genre_in", q.GenreIn)
addSlice2InterfaceMap(m, "genre_not_in", q.GenreNotIn)
addSlice2InterfaceMap(m, "tag_in", q.TagIn)
addSlice2InterfaceMap(m, "tag_not_in", q.TagNotIn)
addSlice2InterfaceMap(m, "source_in", q.SourceIn)
addSlice2InterfaceMap(m, "sort", q.Sort)
return m
}
type MediaSort string
const (
MediaSortId MediaSort = "ID"
MediaSortIdDesc MediaSort = "ID_DESC"
MediaSortTitleRomaji MediaSort = "TITLE_ROMAJI"
MediaSortTitleRomajiDesc MediaSort = "TITLE_ROMAJI_DESC"
MediaSortTitleEnglish MediaSort = "TITLE_ENGLISH"
MediaSortTitleEnglishDesc MediaSort = "TITLE_ENGLISH_DESC"
MediaSortTitleNative MediaSort = "TITLE_NATIVE"
MediaSortTitleNativeDesc MediaSort = "TITLE_NATIVE_DESC"
MediaSortType MediaSort = "TYPE"
MediaSortTypeDesc MediaSort = "TYPE_DESC"
MediaSortFormat MediaSort = "FORMAT"
MediaSortFormatDesc MediaSort = "FORMAT_DESC"
MediaSortStartDate MediaSort = "START_DATE"
MediaSortStartDateDesc MediaSort = "START_DATE_DESC"
MediaSortEndDate MediaSort = "END_DATE"
MediaSortEndDateDesc MediaSort = "END_DATE_DESC"
MediaSortScore MediaSort = "SCORE"
MediaSortScoreDesc MediaSort = "SCORE_DESC"
MediaSortPopularity MediaSort = "POPULARITY"
MediaSortPopularityDesc MediaSort = "POPULARITY_DESC"
MediaSortTrending MediaSort = "TRENDING"
MediaSortTrendingDesc MediaSort = "TRENDING_DESC"
MediaSortEpisodes MediaSort = "EPISODES"
MediaSortEpisodesDesc MediaSort = "EPISODES_DESC"
MediaSortDuration MediaSort = "DURATION"
MediaSortDurationDesc MediaSort = "DURATION_DESC"
MediaSortStatus MediaSort = "STATUS"
MediaSortStatusDesc MediaSort = "STATUS_DESC"
MediaSortChapters MediaSort = "CHAPTERS"
MediaSortChaptersDesc MediaSort = "CHAPTERS_DESC"
MediaSortVolumes MediaSort = "VOLUMES"
MediaSortVolumesDesc MediaSort = "VOLUMES_DESC"
MediaSortUpdatedAt MediaSort = "UPDATED_AT"
MediaSortUpdatedAtDesc MediaSort = "UPDATED_AT_DESC"
MediaSortSearchMatch MediaSort = "SEARCH_MATCH"
MediaSortFavourites MediaSort = "FAVOURITES"
MediaSortFavouritesDesc MediaSort = "FAVOURITES_DESC"
)

111
sub_selections.go Normal file
View File

@ -0,0 +1,111 @@
package anilist
const (
subSelectionAiringSchedule = `{
id
mediaId
airingAt
timeUntilAiring
episode
media ` + subSelectionMedia + `
}`
subSelectionMediaList = `{
id
userId
mediaId
status
score
progress
progressVolumes
repeat
priority
private
notes
hiddenFromStatusLists
startedAt ` + subSelectionFuzzyDate + `
completedAt ` + subSelectionFuzzyDate + `
updatedAt
createdAt
media ` + subSelectionMedia + `
user ` + subSelectionUser + `
}`
subSelectionUser = `{
id
name
}`
subSelectionMedia = `{
id
title {
romaji
english
native
userPreferred
}
type
format
status
description
startDate ` + subSelectionFuzzyDate + `
endDate ` + subSelectionFuzzyDate + `
season
seasonYear
seasonInt
episodes
duration
chapters
volumes
countryOfOrigin
isLicensed
source
hashtag
trailer {
id
site
thumbnail
}
updatedAt
coverImage {
extraLarge
large
medium
color
}
bannerImage
genres
synonyms
averageScore
meanScore
popularity
isLocked
trending
favourites
tags {
id
name
description
category
rank
isGeneralSpoiler
isMediaSpoiler
isAdult
userId
}
}`
subSelectionFuzzyDate = `{
year
month
day
}`
subSelectionPageInfo = `{
total
currentPage
lastPage
hasNextPage
perPage
}`
)

214
types.go Normal file
View File

@ -0,0 +1,214 @@
package anilist
import (
"time"
)
type User struct {
ID int `json:"id,omitempty"`
Name string `json:"name,omitempty"`
}
type Media struct {
ID int `json:"id"`
Title MediaTitle `json:"title"`
Type MediaType `json:"type"`
Format MediaFormat `json:"format"`
Status MediaStatus `json:"status"`
Description string `json:"description"`
StartDate FuzzyDate `json:"startDate"`
EndDate FuzzyDate `json:"endDate"`
Season MediaSeason `json:"season"`
SeasonYear int `json:"seasonYear"`
SeasonInt int `json:"seasonInt"`
Episodes int `json:"episodes"`
Duration Minutes `json:"duration"`
Chapters int `json:"chapters"`
Volumes int `json:"volumes"`
CountryOfOrigin string `json:"countryOfOrigin"`
Licensed bool `json:"isLicensed"`
Source MediaSource `json:"source"`
Hashtag string `json:"hashtag"`
Trailer MediaTrailer `json:"trailer"`
UpdatedAt UnixTime `json:"updatedAt"`
CoverImage MediaCoverImage `json:"coverImage"`
BannerImage string `json:"bannerImage"`
Genres []string `json:"genres"`
Synonyms []string `json:"synonyms"`
AverageScore int `json:"averageScore"`
MeanScore int `json:"meanScore"`
Popularity int `json:"popularity"`
Locked bool `json:"isLocked"`
Trending int `json:"trending"`
Favourites int `json:"favourites"`
Tags []MediaTag `json:"tags"`
//TODO
}
type MediaTitle struct {
Romaji string `json:"romaji"`
English string `json:"english"`
Native string `json:"native"`
UserPreferred string `json:"userPreferred"`
}
type MediaType string
const (
MediaTypeAnime MediaType = "ANIME"
MediaTypeManga MediaType = "MANGA"
)
type MediaFormat string
const (
MediaFormatTV MediaFormat = "TV"
MediaFormatTVShort MediaFormat = "TV_SHORT"
MediaFormatMovie MediaFormat = "MOVIE"
MediaFormatSpecial MediaFormat = "SPECIAL"
MediaFormatOVA MediaFormat = "OVA"
MediaFormatONA MediaFormat = "ONA"
MediaFormatMusic MediaFormat = "MUSIC"
MediaFormatManga MediaFormat = "MANGA"
MediaFormatNovel MediaFormat = "NOVEL"
MediaFormatOneShot MediaFormat = "ONE_SHOT"
)
type MediaStatus string
const (
MediaStatusFinished MediaStatus = "FINISHED"
MediaStatusReleasing MediaStatus = "RELEASING"
MediaStatusNotYetReleased MediaStatus = "NOT_YET_RELEASED"
MediaStatusCancelled MediaStatus = "CANCELLED"
)
type MediaSeason string
const (
MediaSeasonWinter MediaSeason = "WINTER"
MediaSeasonSpring MediaSeason = "SPRING"
MediaSeasonSummer MediaSeason = "SUMMER"
MediaSeasonFall MediaSeason = "FALL"
)
type MediaSource string
const (
MediaSourceOriginal MediaSource = "ORIGINAL"
MediaSourceManga MediaSource = "MANGA"
MediaSourceLightNovel MediaSource = "LIGHT_NOVEL"
MediaSourceVisualNovel MediaSource = "VISUAL_NOVEL"
MediaSourceVideoGame MediaSource = "VIDEO_GAME"
MediaSourceOther MediaSource = "OTHER"
MediaSourceNovel MediaSource = "NOVEL"
MediaSourceDoujinshi MediaSource = "DOUJINSHI"
MediaSourceAnime MediaSource = "ANIME"
MediaSourceWebNovel MediaSource = "WEB_NOVEL"
MediaSourceLiveAction MediaSource = "LIVE_ACTION"
MediaSourceGame MediaSource = "GAME"
MediaSourceComic MediaSource = "COMIC"
MediaSourceMultimediaProject MediaSource = "MULTIMEDIA_PROJECT"
MediaSourcePictureBook MediaSource = "PICTURE_BOOK"
)
type MediaTrailer struct {
ID string `json:"id"`
Site string `json:"site"`
Thumbnail string `json:"thumbnail"`
}
type MediaCoverImage struct {
ExtraLarge string `json:"extraLarge"`
Large string `json:"large"`
Medium string `json:"medium"`
Color string `json:"color"`
}
type MediaTag struct {
ID int `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Category string `json:"category"`
Rank int `json:"rank"`
GeneralSpoiler bool `json:"isGeneralSpoiler"`
MediaSpoiler bool `json:"isMediaSpoiler"`
Adult bool `json:"isAdult"`
UserID int `json:"userId"`
}
type FuzzyDate struct {
Year int `json:"year"`
Month int `json:"month"`
Day int `json:"day"`
}
func (d FuzzyDate) Time() time.Time {
return time.Date(d.Year, time.Month(d.Month), d.Day, 0, 0, 0, 0, time.UTC)
}
type FuzzyDateInt int // YYYYMMDD
func (d FuzzyDateInt) Time() time.Time {
year, month, day := int(d/10000), int(d/100%100), int(d%100)
return time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.UTC)
}
type UnixTime int64
func (t UnixTime) Time() time.Time {
return time.Unix(int64(t), 0)
}
type Minutes int
func (d Minutes) Duration() time.Duration {
return time.Duration(d) * time.Minute
}
type Seconds int
func (d Seconds) Duration() time.Duration {
return time.Duration(d) * time.Second
}
type MediaList struct {
ID int `json:"id"`
UserID int `json:"userId"`
MediaID int `json:"mediaId"`
Status MediaListStatus `json:"status"`
Score float64 `json:"score"`
Progress int `json:"progress"`
ProgressVolumes int `json:"progressVolumes"`
Repeat int `json:"repeat"`
Priority int `json:"priority"`
Private bool `json:"private"`
Notes string `json:"notes"`
HiddenFromStatusLists bool `json:"hiddenFromStatusLists"`
StartedAt FuzzyDate `json:"startedAt"`
CompletedAt FuzzyDate `json:"completedAt"`
UpdatedAt UnixTime `json:"updatedAt"`
CreatedAt UnixTime `json:"createdAt"`
Media *Media `json:"media"`
User *User `json:"user"`
}
type MediaListStatus string
const (
MediaListStatusCurrent MediaSource = "CURRENT"
MediaListStatusPlanning MediaSource = "PLANNING"
MediaListStatusCompleted MediaSource = "COMPLETED"
MediaListStatusDropped MediaSource = "DROPPED"
MediaListStatusPaused MediaSource = "PAUSED"
MediaListStatusRepeating MediaSource = "REPEATING"
)
type AiringSchedule struct {
ID int `json:"id"`
MediaID int `json:"mediaId"`
AiringAt UnixTime `json:"airingAt"`
TimeUntilAiring Seconds `json:"timeUntilAiring"`
Episode int `json:"episode"`
Media *Media `json:"media"`
}

58
user.go Normal file
View File

@ -0,0 +1,58 @@
package anilist
func (api *Api) GetUserByName(name string) (*User, error) {
query := `query ($name: String) {
User (name: $name) ` + subSelectionUser + `
}`
vars := map[string]interface{}{
"name": name,
}
resp := responseObj[struct {
User *User
}]{}
err := request(api, query, vars, &resp)
if err != nil {
return nil, err
}
return resp.Data.User, nil
}
func (api *Api) GetUserByID(id int) (*User, error) {
query := `query ($id: Int) {
User (id: $id) ` + subSelectionUser + `
}`
vars := map[string]interface{}{
"id": id,
}
resp := responseObj[struct {
User *User
}]{}
err := request(api, query, vars, &resp)
if err != nil {
return nil, err
}
return resp.Data.User, nil
}
func (api *Api) SearchUsers(search string, onError func(error)) <-chan *User {
vars := map[string]interface{}{"search": search}
resp := responseObj[*page[User]]{}
return requestPaged(api, searchUsersQuery, vars, &resp, onError)
}
const (
searchUsersQuery = `query ($search: String, $page: Int) {
Page (page: $page) {
pageInfo ` + subSelectionPageInfo + `
users (search: $search) ` + subSelectionUser + `
}
}`
)

24
utils.go Normal file
View File

@ -0,0 +1,24 @@
package anilist
import (
"reflect"
"time"
)
func addValue2InterfaceMap[K, T comparable](m map[K]interface{}, key K, value T) {
if value != *new(T) {
if reflect.TypeOf(new(T)).Elem() == reflect.TypeOf(new(time.Time)).Elem() {
var t interface{} = value
m[key] = t.(time.Time).Unix()
return
}
m[key] = value
}
}
func addSlice2InterfaceMap[K, T comparable](m map[K]interface{}, key K, value []T) {
if value != nil && len(value) > 0 {
m[key] = value
}
}