diff options
| author | Fuwn <[email protected]> | 2024-07-07 21:55:32 -0700 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2024-07-07 21:55:32 -0700 |
| commit | 3c85251b260d7abbb1b3f48f2621be9b4e45124c (patch) | |
| tree | 654ba8280d2c7105049527a00e9c2fde121d15fa /src/lib/Data | |
| parent | feat(attributions): add a few (diff) | |
| download | due.moe-3c85251b260d7abbb1b3f48f2621be9b4e45124c.tar.xz due.moe-3c85251b260d7abbb1b3f48f2621be9b4e45124c.zip | |
feat(tools): sequel catcher
Diffstat (limited to 'src/lib/Data')
| -rw-r--r-- | src/lib/Data/AniList/media.ts | 133 | ||||
| -rw-r--r-- | src/lib/Data/AniList/prequels.ts | 10 |
2 files changed, 110 insertions, 33 deletions
diff --git a/src/lib/Data/AniList/media.ts b/src/lib/Data/AniList/media.ts index 17b428cd..89d6e339 100644 --- a/src/lib/Data/AniList/media.ts +++ b/src/lib/Data/AniList/media.ts @@ -5,6 +5,7 @@ import manga from '$stores/manga'; import settings from '$stores/settings'; import lastPruneTimes from '$stores/lastPruneTimes'; import { options as getOptions, type Options } from '$lib/Notification/options'; +import type { PrequelRelations } from './prequels'; export enum Type { Anime, @@ -17,26 +18,38 @@ export interface MediaTitle { native: string; } +export type MediaStatus = 'FINISHED' | 'RELEASING' | 'NOT_YET_RELEASED' | 'CANCELLED' | 'HIATUS'; +export type MediaListEntryStatus = + | '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'; +export type MediaSeason = 'WINTER' | 'SPRING' | 'SUMMER' | 'FALL'; + export interface Media { id: number; idMal: number; - status: 'FINISHED' | 'RELEASING' | 'NOT_YET_RELEASED' | 'CANCELLED' | 'HIATUS'; - type: 'ANIME' | 'MANGA'; + status: MediaStatus; + type: MediaType; episodes: number; chapters: number; volumes: number; duration: number; - format: - | 'TV' - | 'TV_SHORT' - | 'MOVIE' - | 'SPECIAL' - | 'OVA' - | 'ONA' - | 'MUSIC' - | 'MANGA' - | 'NOVEL' - | 'ONE_SHOT'; + format: MediaFormat; title: MediaTitle; nextAiringEpisode?: { episode: number; @@ -47,7 +60,7 @@ export interface Media { mediaListEntry?: { progress: number; progressVolumes: number; - status: 'CURRENT' | 'PLANNING' | 'COMPLETED' | 'DROPPED' | 'PAUSED' | 'REPEATING'; + status: MediaListEntryStatus; score: number; repeat: number; startedAt: { @@ -73,8 +86,9 @@ export interface Media { rank: number; }[]; genres: string[]; - season: 'WINTER' | 'SPRING' | 'SUMMER' | 'FALL'; + season: MediaSeason; isAdult: boolean; + relations: PrequelRelations; } export const flattenLists = ( @@ -118,12 +132,12 @@ export const flattenLists = ( return processedList(flattenedList, dueInclude); }; -const collectionQueryTemplate = (type: Type, userId: number, includeCompleted: boolean) => +const collectionQueryTemplate = (type: Type, userId: number, options: CollectionOptions = {}) => `{ MediaListCollection( userId: ${userId}, type: ${type === Type.Anime ? 'ANIME' : 'MANGA'} - ${includeCompleted ? '' : ', status_not_in: [ COMPLETED ]'}) { + ${options.includeCompleted ? '' : ', status_not_in: [ COMPLETED ]'}) { lists { name entries { media { @@ -138,6 +152,31 @@ const collectionQueryTemplate = (type: Type, userId: number, includeCompleted: b startDate { year } endDate { year } coverImage { extraLarge } + ${ + options.includeRelations + ? ` + relations { + edges { + relationType + node { + id + status + title { + english + romaji + } + episodes + mediaListEntry { + status + progress + } + coverImage { extraLarge } + } + } + } + ` + : '' + } } } } @@ -150,23 +189,17 @@ interface CollectionOptions { all?: boolean; addNotification?: (preferences: Options) => void; notificationType?: string; -} - -interface NonNullCollectionOptions { - includeCompleted: boolean; - forcePrune: boolean; - all?: boolean; - addNotification?: (preferences: Options) => void; - notificationType?: string; + includeRelations?: boolean; } const assignDefaultOptions = (options: CollectionOptions) => { - const nonNullOptions: NonNullCollectionOptions = { + const nonNullOptions: CollectionOptions = { includeCompleted: false, forcePrune: false, all: false, addNotification: undefined, - notificationType: undefined + notificationType: undefined, + includeRelations: false }; if (options.includeCompleted !== undefined) @@ -177,6 +210,8 @@ const assignDefaultOptions = (options: CollectionOptions) => { nonNullOptions.addNotification = options.addNotification; if (options.notificationType !== undefined) nonNullOptions.notificationType = options.notificationType; + if (options.includeRelations !== undefined) + nonNullOptions.includeRelations = options.includeRelations; return nonNullOptions; }; @@ -229,7 +264,7 @@ export const mediaListCollection = async ( Accept: 'application/json' }, body: JSON.stringify({ - query: collectionQueryTemplate(type, userIdentity.id, options.includeCompleted) + query: collectionQueryTemplate(type, userIdentity.id, options) }) }) ).json(); @@ -277,7 +312,7 @@ export const publicMediaListCollection = async (userId: number, type: Type): Pro Accept: 'application/json' }, body: JSON.stringify({ - query: collectionQueryTemplate(type, userId, false) + query: collectionQueryTemplate(type, userId, {}) }) }) ).json() @@ -441,3 +476,43 @@ export const mediaCover = async (id: number) => }) ).json() )['data']['Media']['coverImage']['extraLarge']; + +export interface UnwatchedRelationMap { + media: Media; + unwatchedRelations: Media[]; +} + +export const filterRelations = (media: Media[]) => { + const unwatchedRelationsMap: UnwatchedRelationMap[] = []; + + for (const mediaItem of media) { + const sequels = mediaItem.relations.edges.filter( + (relation) => + relation.relationType === 'SEQUEL' && + !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) { + const unwatchedRelations: Media[] = []; + + unwatchedRelations.push(...sequels); + + if (unwatchedRelations.length === 0) { + continue; + } + + unwatchedRelationsMap.push({ + media: mediaItem, + unwatchedRelations + }); + } + } + + return unwatchedRelationsMap; +}; diff --git a/src/lib/Data/AniList/prequels.ts b/src/lib/Data/AniList/prequels.ts index 3009e9ba..f92cde26 100644 --- a/src/lib/Data/AniList/prequels.ts +++ b/src/lib/Data/AniList/prequels.ts @@ -1,5 +1,5 @@ import type { AniListAuthorisation } from './identity'; -import type { MediaTitle } from './media'; +import type { MediaListEntryStatus, MediaSeason, MediaStatus, MediaTitle } from './media'; export interface MediaPrequel { id: number; @@ -20,14 +20,16 @@ export interface MediaPrequel { }; } -interface PrequelRelations { +export interface PrequelRelations { edges: { relationType: string; node: { + id: number; title: MediaTitle; episodes: number; + status: MediaStatus; mediaListEntry: { - status: string; + status: MediaListEntryStatus; progress: number; }; coverImage: { @@ -68,7 +70,7 @@ const prequelsPage = async ( page: number, anilistAuthorisation: AniListAuthorisation, year: number, - season: 'WINTER' | 'SPRING' | 'SUMMER' | 'FALL' + season: MediaSeason ): Promise<PrequelsPage> => await ( await fetch('https://graphql.anilist.co', { |