diff options
| author | Fuwn <[email protected]> | 2024-10-09 00:41:20 -0700 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2024-10-09 00:41:43 -0700 |
| commit | 998b63a35256ac985a5a2714dd1ca451af4dfd8a (patch) | |
| tree | 50796121a9d5ab0330fdc5d7e098bda2860d9726 /src/lib/Data/AniList/media.ts | |
| parent | feat(graphql): add badgeCount field (diff) | |
| download | due.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.ts | 778 |
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; }; |