initial commit
This commit is contained in:
commit
c8669da106
53
airingschedule.go
Normal file
53
airingschedule.go
Normal 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
100
api.go
Normal 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
|
||||
}
|
98
media.go
Normal file
98
media.go
Normal 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
51
medialist.go
Normal 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
45
page.go
Normal 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
257
queries.go
Normal 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
111
sub_selections.go
Normal 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
214
types.go
Normal 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
58
user.go
Normal 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
24
utils.go
Normal 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
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user