diff --git a/.env.sample b/.env.sample index aa7547c0..a2d9fdb3 100644 --- a/.env.sample +++ b/.env.sample @@ -140,6 +140,11 @@ BUILTIN_ANIMETOSHO_URL="https://feed.animetosho.org" # ============================================================================= # # ANIME DATABASE # ============================================================================= + +# Customise the level of detail that is loaded and available for AIOStreams and the Anime API. +# Choices: none, required, full +# Default: required +ANIME_DB_LEVEL_OF_DETAIL=required # Control how often the datasources for the local anime database will refresh. ANIME_DB_FRIBB_MAPPINGS_REFRESH_INTERVAL=86400000 diff --git a/packages/core/src/utils/anime-database.ts b/packages/core/src/utils/anime-database.ts index 6dafd6c1..4d2e47c5 100644 --- a/packages/core/src/utils/anime-database.ts +++ b/packages/core/src/utils/anime-database.ts @@ -203,6 +203,13 @@ const ManamiEntrySchema = z.object({ }); type ManamiEntry = z.infer; +const MinimisedManamiEntrySchema = ManamiEntrySchema.pick({ + title: true, + animeSeason: true, + synonyms: true, +}); +type MinimisedManamiEntry = z.infer; + const KitsuEntrySchema = z .object({ fanartLogoId: z.coerce.number().optional(), @@ -304,8 +311,63 @@ const ExtendedAnitraktTvEntrySchema = z type ExtendedAnitraktTvEntry = z.infer; +const AnimeEntrySchema = z + .object({ + mappings: z + .record( + z.string(), + z.union([z.string(), z.number(), z.null(), z.undefined()]) + ) + .optional(), + imdb: z + .object({ + fromImdbSeason: z.number().optional(), + fromImdbEpisode: z.number().optional(), + title: z.string().optional(), + }) + .nullable(), + fanart: z + .object({ + logoId: z.number(), + }) + .nullable(), + trakt: z + .object({ + title: z.string(), + slug: z.string(), + isSplitCour: z.boolean().optional(), + season: z + .object({ + id: z.number(), + number: z.number(), + externals: z.object({ + tvdb: z.number().nullable(), + tmdb: z.number().nullable(), + imdb: z.string().nullable().optional(), + }), + }) + .nullable() + .optional(), + }) + .nullable(), + title: z.string().optional(), + animeSeason: z + .object({ + season: AnimeSeason, + year: z.number().nullable(), + }) + .optional(), + synonyms: z.array(z.string()).optional(), + }) + .nullable(); + +type AnimeEntry = z.infer; + type MappingIdMap = Map>; -type ManamiIdMap = Map>; +type ManamiIdMap = Map< + IdType, + Map +>; type KitsuIdMap = Map; type ExtendedAnitraktMoviesIdMap = Map; type ExtendedAnitraktTvIdMap = Map; @@ -353,6 +415,14 @@ export class AnimeDatabase { return; } + if (Env.ANIME_DB_LEVEL_OF_DETAIL === 'none') { + logger.info( + 'AnimeDatabase detail level is none, skipping initialisation.' + ); + this.isInitialised = true; + return; + } + logger.info('Starting initial refresh of all anime data sources...'); // Perform initial fetch for all datasets concurrently const refreshPromises = Object.values(DATA_SOURCES).map((dataSource) => @@ -375,7 +445,7 @@ export class AnimeDatabase { return false; } - public getEntryById(idType: IdType, idValue: string | number) { + public getEntryById(idType: IdType, idValue: string | number): AnimeEntry { const getFromMap = (map: Map | undefined, key: any) => map?.get(key) || map?.get(key.toString()) || map?.get(Number(key)); @@ -633,7 +703,14 @@ export class AnimeDatabase { if (idValue) { const existingEntry = newManamiById.get(idType)?.get(idValue); if (!existingEntry) { - newManamiById.get(idType)?.set(idValue, entry); + newManamiById + .get(idType) + ?.set( + idValue, + Env.ANIME_DB_LEVEL_OF_DETAIL === 'required' + ? this.minimiseManamiEntry(entry) + : entry + ); } } } @@ -646,6 +723,14 @@ export class AnimeDatabase { ); } + private minimiseManamiEntry(entry: ManamiEntry): MinimisedManamiEntry { + return { + title: entry.title, + animeSeason: entry.animeSeason, + synonyms: entry.synonyms, + }; + } + private async loadKitsuImdbMapping(): Promise { const start = Date.now(); const fileContents = await this.readLocalFile( diff --git a/packages/core/src/utils/env.ts b/packages/core/src/utils/env.ts index 013adce8..8b0ff04e 100644 --- a/packages/core/src/utils/env.ts +++ b/packages/core/src/utils/env.ts @@ -376,6 +376,11 @@ export const Env = cleanEnv(process.env, { default: true, desc: 'Enable the search API. If true, the search API will be enabled.', }), + ANIME_DB_LEVEL_OF_DETAIL: str({ + default: 'required', + desc: 'Detail level for the anime database. none: no anime database, required: only load the required databases, full: load all data', + choices: ['none', 'required', 'full'], + }), ANIME_DB_FRIBB_MAPPINGS_REFRESH_INTERVAL: num({ default: 24 * 60 * 60 * 1000, // 24 hours desc: 'Interval for refreshing the anime mappings in milliseconds',