aboutsummaryrefslogtreecommitdiff
path: root/src/lib/Data/AniList/media.ts
diff options
context:
space:
mode:
authorFuwn <[email protected]>2024-10-09 00:41:20 -0700
committerFuwn <[email protected]>2024-10-09 00:41:43 -0700
commit998b63a35256ac985a5a2714dd1ca451af4dfd8a (patch)
tree50796121a9d5ab0330fdc5d7e098bda2860d9726 /src/lib/Data/AniList/media.ts
parentfeat(graphql): add badgeCount field (diff)
downloaddue.moe-998b63a35256ac985a5a2714dd1ca451af4dfd8a.tar.xz
due.moe-998b63a35256ac985a5a2714dd1ca451af4dfd8a.zip
chore(prettier): use spaces instead of tabs
Diffstat (limited to 'src/lib/Data/AniList/media.ts')
-rw-r--r--src/lib/Data/AniList/media.ts778
1 files changed, 389 insertions, 389 deletions
diff --git a/src/lib/Data/AniList/media.ts b/src/lib/Data/AniList/media.ts
index d3b38005..f8aeb401 100644
--- a/src/lib/Data/AniList/media.ts
+++ b/src/lib/Data/AniList/media.ts
@@ -8,133 +8,133 @@ import { options as getOptions, type Options } from '$lib/Notification/options';
import type { PrequelRelation, PrequelRelations } from './prequels';
export enum Type {
- Anime,
- Manga
+ Anime,
+ Manga
}
export interface MediaTitle {
- romaji: string;
- english: string;
- native: string;
+ romaji: string;
+ english: string;
+ native: string;
}
export type MediaStatus = 'FINISHED' | 'RELEASING' | 'NOT_YET_RELEASED' | 'CANCELLED' | 'HIATUS';
export type MediaListEntryStatus =
- | 'CURRENT'
- | 'PLANNING'
- | 'COMPLETED'
- | 'DROPPED'
- | 'PAUSED'
- | 'REPEATING';
+ | 'CURRENT'
+ | 'PLANNING'
+ | 'COMPLETED'
+ | 'DROPPED'
+ | 'PAUSED'
+ | 'REPEATING';
export type MediaType = 'ANIME' | 'MANGA';
export type MediaFormat =
- | 'TV'
- | 'TV_SHORT'
- | 'MOVIE'
- | 'SPECIAL'
- | 'OVA'
- | 'ONA'
- | 'MUSIC'
- | 'MANGA'
- | 'NOVEL'
- | 'ONE_SHOT';
+ | 'TV'
+ | 'TV_SHORT'
+ | 'MOVIE'
+ | 'SPECIAL'
+ | 'OVA'
+ | 'ONA'
+ | 'MUSIC'
+ | 'MANGA'
+ | 'NOVEL'
+ | 'ONE_SHOT';
export type MediaSeason = 'WINTER' | 'SPRING' | 'SUMMER' | 'FALL';
export interface Media {
- id: number;
- idMal: number;
- status: MediaStatus;
- type: MediaType;
- episodes: number;
- chapters: number;
- volumes: number;
- duration: number;
- format: MediaFormat;
- title: MediaTitle;
- nextAiringEpisode?: {
- episode: number;
- airingAt?: number;
- nativeAiringAt?: number;
- };
- synonyms: string[];
- mediaListEntry?: {
- progress: number;
- progressVolumes: number;
- status: MediaListEntryStatus;
- score: number;
- repeat: number;
- startedAt: {
- year: number;
- };
- completedAt: {
- year: number;
- };
- createdAt: number;
- updatedAt: number;
- };
- startDate: {
- year: number;
- };
- endDate: {
- year: number;
- };
- coverImage: {
- extraLarge: string;
- medium: string;
- };
- tags: {
- name: string;
- rank: number;
- }[];
- genres: string[];
- season: MediaSeason;
- isAdult: boolean;
- relations: PrequelRelations;
+ id: number;
+ idMal: number;
+ status: MediaStatus;
+ type: MediaType;
+ episodes: number;
+ chapters: number;
+ volumes: number;
+ duration: number;
+ format: MediaFormat;
+ title: MediaTitle;
+ nextAiringEpisode?: {
+ episode: number;
+ airingAt?: number;
+ nativeAiringAt?: number;
+ };
+ synonyms: string[];
+ mediaListEntry?: {
+ progress: number;
+ progressVolumes: number;
+ status: MediaListEntryStatus;
+ score: number;
+ repeat: number;
+ startedAt: {
+ year: number;
+ };
+ completedAt: {
+ year: number;
+ };
+ createdAt: number;
+ updatedAt: number;
+ };
+ startDate: {
+ year: number;
+ };
+ endDate: {
+ year: number;
+ };
+ coverImage: {
+ extraLarge: string;
+ medium: string;
+ };
+ tags: {
+ name: string;
+ rank: number;
+ }[];
+ genres: string[];
+ season: MediaSeason;
+ isAdult: boolean;
+ relations: PrequelRelations;
}
export const flattenLists = (
- lists: { name: string; entries: { media: Media }[] }[],
- all = false
+ lists: { name: string; entries: { media: Media }[] }[],
+ all = false
) => {
- if (lists === undefined) return [];
-
- const flattenedList: Media[] = [];
- const markedMediaIds: number[] = [];
- let dueInclude = false;
- const processedList = (list: Media[], include: boolean) =>
- list
- .filter((media) =>
- include ? markedMediaIds.includes(media.id) : !markedMediaIds.includes(media.id)
- )
- .filter(
- (item, index, self) =>
- self.findIndex((itemToCompare) => itemToCompare.id === item.id) === index
- );
-
- if (all) {
- for (const list of lists) flattenedList.push(...list.entries.map((entry) => entry.media));
- } else {
- for (const list of lists) {
- if (list.name.toLowerCase().includes('#dueinclude')) {
- dueInclude = true;
-
- markedMediaIds.splice(0, markedMediaIds.length);
- markedMediaIds.push(...list.entries.map((entry) => entry.media.id));
- }
-
- if (dueInclude) continue;
-
- if (list.name.toLowerCase().includes('#dueignore'))
- markedMediaIds.push(...list.entries.map((entry) => entry.media.id));
- else flattenedList.push(...list.entries.map((entry) => entry.media));
- }
- }
-
- return processedList(flattenedList, dueInclude);
+ if (lists === undefined) return [];
+
+ const flattenedList: Media[] = [];
+ const markedMediaIds: number[] = [];
+ let dueInclude = false;
+ const processedList = (list: Media[], include: boolean) =>
+ list
+ .filter((media) =>
+ include ? markedMediaIds.includes(media.id) : !markedMediaIds.includes(media.id)
+ )
+ .filter(
+ (item, index, self) =>
+ self.findIndex((itemToCompare) => itemToCompare.id === item.id) === index
+ );
+
+ if (all) {
+ for (const list of lists) flattenedList.push(...list.entries.map((entry) => entry.media));
+ } else {
+ for (const list of lists) {
+ if (list.name.toLowerCase().includes('#dueinclude')) {
+ dueInclude = true;
+
+ markedMediaIds.splice(0, markedMediaIds.length);
+ markedMediaIds.push(...list.entries.map((entry) => entry.media.id));
+ }
+
+ if (dueInclude) continue;
+
+ if (list.name.toLowerCase().includes('#dueignore'))
+ markedMediaIds.push(...list.entries.map((entry) => entry.media.id));
+ else flattenedList.push(...list.entries.map((entry) => entry.media));
+ }
+ }
+
+ return processedList(flattenedList, dueInclude);
};
const collectionQueryTemplate = (type: Type, userId: number, options: CollectionOptions = {}) =>
- `{
+ `{
MediaListCollection(
userId: ${userId},
type: ${type === Type.Anime ? 'ANIME' : 'MANGA'}
@@ -154,8 +154,8 @@ const collectionQueryTemplate = (type: Type, userId: number, options: Collection
endDate { year }
coverImage { extraLarge medium }
${
- options.includeRelations
- ? `
+ options.includeRelations
+ ? `
relations {
edges {
relationType
@@ -169,8 +169,8 @@ const collectionQueryTemplate = (type: Type, userId: number, options: Collection
}
}
`
- : ''
- }
+ : ''
+ }
}
}
}
@@ -178,190 +178,190 @@ const collectionQueryTemplate = (type: Type, userId: number, options: Collection
}`;
interface CollectionOptions {
- includeCompleted?: boolean;
- forcePrune?: boolean;
- all?: boolean;
- addNotification?: (preferences: Options) => void;
- notificationType?: string;
- includeRelations?: boolean;
+ includeCompleted?: boolean;
+ forcePrune?: boolean;
+ all?: boolean;
+ addNotification?: (preferences: Options) => void;
+ notificationType?: string;
+ includeRelations?: boolean;
}
const assignDefaultOptions = (options: CollectionOptions) => {
- const nonNullOptions: CollectionOptions = {
- includeCompleted: false,
- forcePrune: false,
- all: false,
- addNotification: undefined,
- notificationType: undefined,
- includeRelations: false
- };
-
- if (options.includeCompleted !== undefined)
- nonNullOptions.includeCompleted = options.includeCompleted;
- if (options.forcePrune !== undefined) nonNullOptions.forcePrune = options.forcePrune;
- if (options.all !== undefined) nonNullOptions.all = options.all;
- if (options.addNotification !== undefined)
- nonNullOptions.addNotification = options.addNotification;
- if (options.notificationType !== undefined)
- nonNullOptions.notificationType = options.notificationType;
- if (options.includeRelations !== undefined)
- nonNullOptions.includeRelations = options.includeRelations;
-
- return nonNullOptions;
+ const nonNullOptions: CollectionOptions = {
+ includeCompleted: false,
+ forcePrune: false,
+ all: false,
+ addNotification: undefined,
+ notificationType: undefined,
+ includeRelations: false
+ };
+
+ if (options.includeCompleted !== undefined)
+ nonNullOptions.includeCompleted = options.includeCompleted;
+ if (options.forcePrune !== undefined) nonNullOptions.forcePrune = options.forcePrune;
+ if (options.all !== undefined) nonNullOptions.all = options.all;
+ if (options.addNotification !== undefined)
+ nonNullOptions.addNotification = options.addNotification;
+ if (options.notificationType !== undefined)
+ nonNullOptions.notificationType = options.notificationType;
+ if (options.includeRelations !== undefined)
+ nonNullOptions.includeRelations = options.includeRelations;
+
+ return nonNullOptions;
};
export const mediaListCollection = async (
- anilistAuthorisation: AniListAuthorisation,
- userIdentity: UserIdentity,
- type: Type,
- mediaCache: string | undefined,
- currentLastPruneAt: string | number,
- inputOptions: CollectionOptions = {}
+ anilistAuthorisation: AniListAuthorisation,
+ userIdentity: UserIdentity,
+ type: Type,
+ mediaCache: string | undefined,
+ currentLastPruneAt: string | number,
+ inputOptions: CollectionOptions = {}
): Promise<Media[]> => {
- if (userIdentity.id === -1 || userIdentity.id === -2) return [];
-
- const options = assignDefaultOptions(inputOptions);
-
- let currentCacheMinutes;
-
- settings.subscribe((value) => (currentCacheMinutes = value.cacheMinutes));
-
- if (String(currentLastPruneAt) === '') {
- if (type === Type.Anime) lastPruneTimes.setKey('anime', new Date().getTime());
- else lastPruneTimes.setKey('manga', new Date().getTime());
- } else {
- if (
- (new Date().getTime() - Number(currentLastPruneAt)) / 1000 / 60 >
- Number(currentCacheMinutes) ||
- options.forcePrune
- ) {
- if (type === Type.Anime) {
- lastPruneTimes.setKey('anime', new Date().getTime());
- anime.set('');
- } else {
- lastPruneTimes.setKey('manga', new Date().getTime());
- manga.set('');
- }
-
- mediaCache = '';
- }
- }
-
- if (mediaCache !== undefined && mediaCache !== '') return JSON.parse(mediaCache);
-
- const userIdResponse = 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: collectionQueryTemplate(type, userIdentity.id, options)
- })
- })
- ).json();
-
- if (
- !userIdResponse['data'] ||
- !userIdResponse['data']['MediaListCollection'] ||
- !userIdResponse['data']['MediaListCollection']['lists']
- )
- return [];
-
- if (mediaCache === '')
- if (type === Type.Anime)
- anime.set(
- JSON.stringify(
- flattenLists(userIdResponse['data']['MediaListCollection']['lists'], options.all)
- )
- );
- else
- manga.set(
- JSON.stringify(
- flattenLists(userIdResponse['data']['MediaListCollection']['lists'], options.all)
- )
- );
-
- if (options.addNotification)
- options.addNotification(
- getOptions({
- heading: options.notificationType ? options.notificationType : Type[type],
- description: 'Re-cached media lists from AniList'
- })
- );
-
- return flattenLists(userIdResponse['data']['MediaListCollection']['lists'], options.all);
+ if (userIdentity.id === -1 || userIdentity.id === -2) return [];
+
+ const options = assignDefaultOptions(inputOptions);
+
+ let currentCacheMinutes;
+
+ settings.subscribe((value) => (currentCacheMinutes = value.cacheMinutes));
+
+ if (String(currentLastPruneAt) === '') {
+ if (type === Type.Anime) lastPruneTimes.setKey('anime', new Date().getTime());
+ else lastPruneTimes.setKey('manga', new Date().getTime());
+ } else {
+ if (
+ (new Date().getTime() - Number(currentLastPruneAt)) / 1000 / 60 >
+ Number(currentCacheMinutes) ||
+ options.forcePrune
+ ) {
+ if (type === Type.Anime) {
+ lastPruneTimes.setKey('anime', new Date().getTime());
+ anime.set('');
+ } else {
+ lastPruneTimes.setKey('manga', new Date().getTime());
+ manga.set('');
+ }
+
+ mediaCache = '';
+ }
+ }
+
+ if (mediaCache !== undefined && mediaCache !== '') return JSON.parse(mediaCache);
+
+ const userIdResponse = 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: collectionQueryTemplate(type, userIdentity.id, options)
+ })
+ })
+ ).json();
+
+ if (
+ !userIdResponse['data'] ||
+ !userIdResponse['data']['MediaListCollection'] ||
+ !userIdResponse['data']['MediaListCollection']['lists']
+ )
+ return [];
+
+ if (mediaCache === '')
+ if (type === Type.Anime)
+ anime.set(
+ JSON.stringify(
+ flattenLists(userIdResponse['data']['MediaListCollection']['lists'], options.all)
+ )
+ );
+ else
+ manga.set(
+ JSON.stringify(
+ flattenLists(userIdResponse['data']['MediaListCollection']['lists'], options.all)
+ )
+ );
+
+ if (options.addNotification)
+ options.addNotification(
+ getOptions({
+ heading: options.notificationType ? options.notificationType : Type[type],
+ description: 'Re-cached media lists from AniList'
+ })
+ );
+
+ return flattenLists(userIdResponse['data']['MediaListCollection']['lists'], options.all);
};
export const publicMediaListCollection = async (userId: number, type: Type): Promise<Media[]> =>
- flattenLists(
- (
- await (
- await fetch('https://graphql.anilist.co', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- Accept: 'application/json'
- },
- body: JSON.stringify({
- query: collectionQueryTemplate(type, userId, {})
- })
- })
- ).json()
- )['data']['MediaListCollection']['lists']
- );
+ flattenLists(
+ (
+ await (
+ await fetch('https://graphql.anilist.co', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ Accept: 'application/json'
+ },
+ body: JSON.stringify({
+ query: collectionQueryTemplate(type, userId, {})
+ })
+ })
+ ).json()
+ )['data']['MediaListCollection']['lists']
+ );
const countMedian = (guesses: number[]) => {
- guesses.sort((a: number, b: number) => a - b);
+ guesses.sort((a: number, b: number) => a - b);
- const mid = Math.floor(guesses.length / 2);
+ const mid = Math.floor(guesses.length / 2);
- return guesses.length % 2 !== 0 ? guesses[mid] : (guesses[mid - 1] + guesses[mid]) / 2;
+ return guesses.length % 2 !== 0 ? guesses[mid] : (guesses[mid - 1] + guesses[mid]) / 2;
};
const countIQR = (guesses: number[]) => {
- guesses.sort((a: number, b: number) => a - b);
+ guesses.sort((a: number, b: number) => a - b);
- const q1 = guesses[Math.floor(guesses.length / 4)];
- const q3 = guesses[Math.ceil(guesses.length * (3 / 4))];
- const iqr = q3 - q1;
+ const q1 = guesses[Math.floor(guesses.length / 4)];
+ const q3 = guesses[Math.ceil(guesses.length * (3 / 4))];
+ const iqr = q3 - q1;
- return guesses.filter((guess: number) => guess >= q1 - 1.5 * iqr && guess <= q3 + 1.5 * iqr);
+ return guesses.filter((guess: number) => guess >= q1 - 1.5 * iqr && guess <= q3 + 1.5 * iqr);
};
const countMode = (guesses: number[]) => {
- const frequency: { [key: number]: number } = {};
- let maximumFrequency = 0;
- let mode = 0;
+ const frequency: { [key: number]: number } = {};
+ let maximumFrequency = 0;
+ let mode = 0;
- for (const guess of guesses) {
- frequency[guess] = (frequency[guess] || 0) + 1;
+ for (const guess of guesses) {
+ frequency[guess] = (frequency[guess] || 0) + 1;
- if (frequency[guess] > maximumFrequency) {
- maximumFrequency = frequency[guess];
- mode = guess;
- }
- }
+ if (frequency[guess] > maximumFrequency) {
+ maximumFrequency = frequency[guess];
+ mode = guess;
+ }
+ }
- return mode;
+ return mode;
};
export const recentMediaActivities = async (
- userIdentity: UserIdentity,
- media: Media,
- method: 'median' | 'iqr_median' | 'iqr_mode' | 'mode'
+ userIdentity: UserIdentity,
+ media: Media,
+ method: 'median' | 'iqr_median' | 'iqr_mode' | 'mode'
): Promise<number | null> => {
- const activities = await (
- await fetch('https://graphql.anilist.co', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- Accept: 'application/json'
- },
- body: JSON.stringify({
- query: `{
+ const activities = await (
+ await fetch('https://graphql.anilist.co', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ Accept: 'application/json'
+ },
+ body: JSON.stringify({
+ query: `{
Page(page: 1) {
activities(mediaId: ${media.id}, sort: ID_DESC) {
... on ListActivity { progress }
@@ -370,136 +370,136 @@ export const recentMediaActivities = async (
MediaList(userId: ${userIdentity.id}, mediaId: ${media.id}) { progress }
}`
- })
- })
- ).json();
- const guesses: number[] = [];
-
- activities['data']['Page']['activities'].forEach((activity: { progress: string }) => {
- if (activity.progress !== null) {
- const progress = Number((activity.progress.match(/\d+$/) || [0])[0]);
-
- if (progress !== 65535) guesses.push(progress);
- }
- });
- guesses.sort((a, b) => b - a);
-
- if (guesses.length) {
- let bestGuess;
-
- switch (method) {
- case 'median':
- {
- bestGuess = countMedian(guesses);
- }
- break;
- case 'iqr_median':
- {
- bestGuess = countMedian(countIQR(guesses));
- }
- break;
- case 'iqr_mode':
- {
- bestGuess = countMode(countIQR(guesses));
- }
- break;
- case 'mode':
- {
- bestGuess = countMode(guesses);
- }
- break;
- default:
- {
- bestGuess = countMedian(guesses);
- }
- break;
- }
-
- // if (guesses.length > 2) {
- // if (guesses.filter((val) => val < 7000).length) {
- // guesses = guesses.filter((val) => val < 7000);
- // }
-
- // const difference = guesses[0] - guesses[1];
- // const inverseDifference = 1 + Math.ceil(25 / (difference + 1));
-
- // if (guesses.length >= inverseDifference) {
- // if (
- // guesses[1] === guesses[inverseDifference] ||
- // guesses[0] - guesses[1] > 500 ||
- // (guesses[0] - guesses[1] > 100 && guesses[1] >= guesses[inverseDifference] - 1)
- // ) {
- // bestGuess = guesses[1];
-
- // if (
- // guesses.length > 15 &&
- // guesses[1] - guesses[2] > 50 &&
- // guesses[2] === guesses[guesses.length - 1]
- // ) {
- // bestGuess = guesses[2];
- // }
- // }
- // }
- // }
-
- // if (activities['data']['MediaList']['progress'] !== null) {
- if (activities['data']['MediaList']['progress'] > bestGuess)
- bestGuess = activities['data']['MediaList']['progress'];
- // }
-
- return Math.round(bestGuess);
- }
-
- return null;
+ })
+ })
+ ).json();
+ const guesses: number[] = [];
+
+ activities['data']['Page']['activities'].forEach((activity: { progress: string }) => {
+ if (activity.progress !== null) {
+ const progress = Number((activity.progress.match(/\d+$/) || [0])[0]);
+
+ if (progress !== 65535) guesses.push(progress);
+ }
+ });
+ guesses.sort((a, b) => b - a);
+
+ if (guesses.length) {
+ let bestGuess;
+
+ switch (method) {
+ case 'median':
+ {
+ bestGuess = countMedian(guesses);
+ }
+ break;
+ case 'iqr_median':
+ {
+ bestGuess = countMedian(countIQR(guesses));
+ }
+ break;
+ case 'iqr_mode':
+ {
+ bestGuess = countMode(countIQR(guesses));
+ }
+ break;
+ case 'mode':
+ {
+ bestGuess = countMode(guesses);
+ }
+ break;
+ default:
+ {
+ bestGuess = countMedian(guesses);
+ }
+ break;
+ }
+
+ // if (guesses.length > 2) {
+ // if (guesses.filter((val) => val < 7000).length) {
+ // guesses = guesses.filter((val) => val < 7000);
+ // }
+
+ // const difference = guesses[0] - guesses[1];
+ // const inverseDifference = 1 + Math.ceil(25 / (difference + 1));
+
+ // if (guesses.length >= inverseDifference) {
+ // if (
+ // guesses[1] === guesses[inverseDifference] ||
+ // guesses[0] - guesses[1] > 500 ||
+ // (guesses[0] - guesses[1] > 100 && guesses[1] >= guesses[inverseDifference] - 1)
+ // ) {
+ // bestGuess = guesses[1];
+
+ // if (
+ // guesses.length > 15 &&
+ // guesses[1] - guesses[2] > 50 &&
+ // guesses[2] === guesses[guesses.length - 1]
+ // ) {
+ // bestGuess = guesses[2];
+ // }
+ // }
+ // }
+ // }
+
+ // if (activities['data']['MediaList']['progress'] !== null) {
+ if (activities['data']['MediaList']['progress'] > bestGuess)
+ bestGuess = activities['data']['MediaList']['progress'];
+ // }
+
+ return Math.round(bestGuess);
+ }
+
+ return null;
};
export const mediaCover = async (id: number) =>
- (
- await (
- await fetch('https://graphql.anilist.co', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- Accept: 'application/json'
- },
- body: JSON.stringify({
- query: `{
+ (
+ await (
+ await fetch('https://graphql.anilist.co', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ Accept: 'application/json'
+ },
+ body: JSON.stringify({
+ query: `{
Media(id: ${id}) { coverImage { extraLarge } }
}`
- })
- })
- ).json()
- )['data']['Media']['coverImage']['extraLarge'];
+ })
+ })
+ ).json()
+ )['data']['Media']['coverImage']['extraLarge'];
export interface UnwatchedRelationMap {
- media: Media;
- unwatchedRelations: PrequelRelation[];
+ media: Media;
+ unwatchedRelations: PrequelRelation[];
}
export const filterRelations = (media: Media[], includeSideStories = false) => {
- const unwatchedRelationsMap: UnwatchedRelationMap[] = [];
-
- for (const mediaItem of media) {
- const sequels = mediaItem.relations.edges.filter(
- (relation: PrequelRelation) =>
- (relation.relationType === 'SEQUEL' ||
- (relation.relationType === 'SIDE_STORY' && includeSideStories)) &&
- !media.some((mediaItem) => mediaItem.id === relation.node.id) &&
- (relation.node.mediaListEntry
- ? relation.node.mediaListEntry.status !== 'COMPLETED'
- : true) &&
- relation.node.episodes &&
- relation.node.status !== 'NOT_YET_RELEASED' &&
- relation.node.status !== 'CANCELLED'
- );
-
- if (sequels.length > 0) {
- unwatchedRelationsMap.push({
- media: mediaItem,
- unwatchedRelations: sequels
- });
- }
- }
-
- return unwatchedRelationsMap;
+ const unwatchedRelationsMap: UnwatchedRelationMap[] = [];
+
+ for (const mediaItem of media) {
+ const sequels = mediaItem.relations.edges.filter(
+ (relation: PrequelRelation) =>
+ (relation.relationType === 'SEQUEL' ||
+ (relation.relationType === 'SIDE_STORY' && includeSideStories)) &&
+ !media.some((mediaItem) => mediaItem.id === relation.node.id) &&
+ (relation.node.mediaListEntry
+ ? relation.node.mediaListEntry.status !== 'COMPLETED'
+ : true) &&
+ relation.node.episodes &&
+ relation.node.status !== 'NOT_YET_RELEASED' &&
+ relation.node.status !== 'CANCELLED'
+ );
+
+ if (sequels.length > 0) {
+ unwatchedRelationsMap.push({
+ media: mediaItem,
+ unwatchedRelations: sequels
+ });
+ }
+ }
+
+ return unwatchedRelationsMap;
};