From 9ba4e7374c6f161bafb7387ce39d3ec5a1115667 Mon Sep 17 00:00:00 2001 From: WebStreamr <210764791+webstreamr@users.noreply.github.com> Date: Fri, 6 Jun 2025 12:08:53 +0000 Subject: [PATCH] feat: implement hourly movie cache warmup --- src/index.ts | 35 +++++++++++++++++++++++++++++++++-- src/utils/tmdb.ts | 8 ++++---- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/index.ts b/src/index.ts index 0a094de..df6170f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,7 +13,7 @@ import { } from './handler'; import { ExtractorRegistry } from './extractor'; import { ConfigureController, ManifestController, StreamController } from './controller'; -import { envGet, envIsProd, Fetcher, StreamResolver } from './utils'; +import { envGet, envIsProd, Fetcher, getImdbIdFromTmdbId, StreamResolver, tmdbFetch } from './utils'; const logger = winston.createLogger({ transports: [ @@ -63,7 +63,9 @@ addon.use((_req: Request, res: Response, next: NextFunction) => { addon.use('/', (new ConfigureController(handlers)).router); addon.use('/', (new ManifestController(handlers)).router); -addon.use('/', (new StreamController(logger, handlers, new StreamResolver(logger))).router); + +const streamResolver = new StreamResolver(logger); +addon.use('/', (new StreamController(logger, handlers, streamResolver)).router); addon.get('/', (_req: Request, res: Response) => { res.redirect('/configure'); @@ -73,3 +75,32 @@ const port = parseInt(envGet('PORT') || '51546'); addon.listen(port, () => { logger.info(`Add-on Repository URL: http://127.0.0.1:${port}/manifest.json`); }); + +const cacheWarmup = async () => { + const ctx = { id: 'warmup', ip: '127.0.0.1', config: { de: 'on', en: 'on', es: 'on', fr: 'on', it: 'on', mx: 'on' } }; + logger.info(`starting cache warmup`, ctx); + + interface MovieResponsePartial { results: { id: number }[] } + const movies = [ + ...(await tmdbFetch(ctx, fetcher, '/movie/now_playing') as MovieResponsePartial)['results'], + ...(await tmdbFetch(ctx, fetcher, '/movie/popular') as MovieResponsePartial)['results'], + ...(await tmdbFetch(ctx, fetcher, '/movie/top_rated') as MovieResponsePartial)['results'], + ...(await tmdbFetch(ctx, fetcher, '/trending/movie/day') as MovieResponsePartial)['results'], + ]; + + const movieIds: number[] = []; + movies.forEach((movie) => { + if (!movieIds.includes(movie.id)) { + movieIds.push(movie.id); + } + }); + + for (const id of movieIds) { + const imdbId = await getImdbIdFromTmdbId(ctx, fetcher, { id, series: undefined, episode: undefined }); + await streamResolver.resolve(ctx, handlers, 'movie', imdbId.id); + } + + logger.info(`warmed up cache with ${movieIds.length} movies`, ctx); + setTimeout(cacheWarmup, 3600000); // 1 hour +}; +setTimeout(cacheWarmup, 10000); diff --git a/src/utils/tmdb.ts b/src/utils/tmdb.ts index 53e9b33..8ca444c 100644 --- a/src/utils/tmdb.ts +++ b/src/utils/tmdb.ts @@ -32,10 +32,10 @@ export const parseTmdbId = (id: string): TmdbId => { }; }; -const fetch = async (ctx: Context, fetcher: Fetcher, url: URL): Promise => { +export const tmdbFetch = async (ctx: Context, fetcher: Fetcher, path: string): Promise => { const config = { 'headers': { Authorization: 'Bearer ' + envGet('TMDB_ACCESS_TOKEN') }, 'Content-Type': 'application/json' }; - return JSON.parse(await fetcher.text(ctx, url, config)); + return JSON.parse(await fetcher.text(ctx, new URL(`https://api.themoviedb.org/3${path}`), config)); }; const imdbTmdbMap = new Map(); @@ -44,7 +44,7 @@ export const getTmdbIdFromImdbId = async (ctx: Context, fetcher: Fetcher, imdbId return { id: imdbTmdbMap.get(imdbId.id) as number, series: imdbId.series, episode: imdbId.episode }; } - const response = await fetch(ctx, fetcher, new URL(`https://api.themoviedb.org/3/find/${imdbId.id}?external_source=imdb_id`)) as FindResponsePartial; + const response = await tmdbFetch(ctx, fetcher, `/find/${imdbId.id}?external_source=imdb_id`) as FindResponsePartial; const id = (imdbId.series ? response.tv_results[0] : response.movie_results[0])?.id; @@ -64,7 +64,7 @@ export const getImdbIdFromTmdbId = async (ctx: Context, fetcher: Fetcher, tmdbId const type = tmdbId.series ? 'tv' : 'movie'; - const response = await fetch(ctx, fetcher, new URL(`https://api.themoviedb.org/3/${type}/${tmdbId.id}/external_ids`)) as ExternalIdsResponsePartial; + const response = await tmdbFetch(ctx, fetcher, `/${type}/${tmdbId.id}/external_ids`) as ExternalIdsResponsePartial; tmdbImdbMap.set(tmdbId.id, response.imdb_id); return { id: response.imdb_id, series: tmdbId.series, episode: tmdbId.episode };