import type { AniListAuthorisation } from './identity'; import type { MediaListEntryStatus, MediaSeason, MediaStatus, MediaTitle } from './media'; export interface MediaPrequel { id: number; title: MediaTitle; episodes: number; seen: number; nextAiringEpisode?: { episode: number; airingAt?: number; }; startDate: { year: number; month: number; day: number; }; coverImage: { extraLarge: string; medium: string; }; } export interface PrequelRelationNode { id: number; title: MediaTitle; episodes: number; status: MediaStatus; mediaListEntry: { status: MediaListEntryStatus; progress: number; }; coverImage: { extraLarge: string; medium: string; }; startDate: { year: number; }; } export interface PrequelRelation { relationType: string; node: PrequelRelationNode; } export interface PrequelRelations { edges: PrequelRelation[]; } interface PrequelsPage { data: { Page: { media: { title: MediaTitle; id: number; relations: PrequelRelations; nextAiringEpisode?: { episode: number; airingAt?: number; }; startDate: { year: number; month: number; day: number; }; coverImage: { extraLarge: string; medium: string; }; }[]; pageInfo: { hasNextPage: boolean; }; }; }; } const prequelsPage = async ( page: number, anilistAuthorisation: AniListAuthorisation, year: number, season: MediaSeason ): Promise => await ( await fetch('https://graphql.anilist.co', { method: 'POST', headers: { Authorization: `${anilistAuthorisation.tokenType} ${anilistAuthorisation.accessToken}`, 'Content-Type': 'application/json', Accept: 'application/json' }, body: JSON.stringify({ query: `{ Page(page: ${page}) { pageInfo { hasNextPage } media(season: ${season}, seasonYear: ${year}) { title { english romaji } coverImage { extraLarge medium } id nextAiringEpisode { episode airingAt } startDate { year month day } relations { edges { relationType node { title { english romaji } episodes mediaListEntry { status progress } coverImage { extraLarge medium } } } } } } }` }) }) ).json(); export const prequels = async ( anilistAuthorisation: AniListAuthorisation, year: number, season: 'WINTER' | 'SPRING' | 'SUMMER' | 'FALL' ): Promise => { const candidates = []; let page = 1; let currentPage = await prequelsPage(page, anilistAuthorisation, year, season); for (const candidate of currentPage.data.Page.media) candidates.push(candidate); while (currentPage['data']['Page']['pageInfo']['hasNextPage']) { for (const candidate of currentPage.data.Page.media) candidates.push(candidate); page += 1; currentPage = await prequelsPage(page, anilistAuthorisation, year, season); } for (const candidate of currentPage.data.Page.media) candidates.push(candidate); const media: MediaPrequel[] = []; for (const candidate of candidates) { let episodes = 0; let seen = 0; for (const relation of candidate.relations.edges) { if (relation.relationType === 'PREQUEL' || relation.relationType === 'PARENT') { if ( relation.node.mediaListEntry === null || relation.node.mediaListEntry.status !== 'COMPLETED' ) { episodes += relation.node.episodes; if (relation.node.mediaListEntry !== null) seen += relation.node.mediaListEntry.progress || 0; } } } if (media.some((m) => m.id === candidate.id)) continue; if (episodes !== 0) media.push({ id: candidate.id, title: candidate.title, episodes, seen, nextAiringEpisode: candidate.nextAiringEpisode, startDate: candidate.startDate, coverImage: candidate.coverImage }); } return media; };