aboutsummaryrefslogtreecommitdiff
path: root/src/lib/Data
diff options
context:
space:
mode:
authorFuwn <[email protected]>2024-07-07 21:55:32 -0700
committerFuwn <[email protected]>2024-07-07 21:55:32 -0700
commit3c85251b260d7abbb1b3f48f2621be9b4e45124c (patch)
tree654ba8280d2c7105049527a00e9c2fde121d15fa /src/lib/Data
parentfeat(attributions): add a few (diff)
downloaddue.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.ts133
-rw-r--r--src/lib/Data/AniList/prequels.ts10
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', {