diff --git a/src/utils/__fixtures__/Fetcher/https:api.themoviedb.org3movie931944external_ids b/src/utils/__fixtures__/Fetcher/https:api.themoviedb.org3movie931944external_ids new file mode 100644 index 0000000..bf11338 --- /dev/null +++ b/src/utils/__fixtures__/Fetcher/https:api.themoviedb.org3movie931944external_ids @@ -0,0 +1 @@ +{"id":931944,"imdb_id":"tt29141112","wikidata_id":"Q123992189","facebook_id":null,"instagram_id":null,"twitter_id":null} \ No newline at end of file diff --git a/src/utils/__fixtures__/Fetcher/https:api.themoviedb.org3tv12345678external_ids.error b/src/utils/__fixtures__/Fetcher/https:api.themoviedb.org3tv12345678external_ids.error new file mode 100644 index 0000000..c31051e --- /dev/null +++ b/src/utils/__fixtures__/Fetcher/https:api.themoviedb.org3tv12345678external_ids.error @@ -0,0 +1 @@ +Fetcher error: 404: Not Found \ No newline at end of file diff --git a/src/utils/__fixtures__/Fetcher/https:api.themoviedb.org3tv42009external_ids b/src/utils/__fixtures__/Fetcher/https:api.themoviedb.org3tv42009external_ids new file mode 100644 index 0000000..3daddc4 --- /dev/null +++ b/src/utils/__fixtures__/Fetcher/https:api.themoviedb.org3tv42009external_ids @@ -0,0 +1 @@ +{"id":42009,"imdb_id":"tt2085059","freebase_mid":"/m/0hhs506","freebase_id":null,"tvdb_id":253463,"tvrage_id":30348,"wikidata_id":"Q558112","facebook_id":"BlackMirrorNetflix","instagram_id":"","twitter_id":"blackmirror"} \ No newline at end of file diff --git a/src/utils/tmdb.test.ts b/src/utils/tmdb.test.ts index d9746ad..76ee9b2 100644 --- a/src/utils/tmdb.test.ts +++ b/src/utils/tmdb.test.ts @@ -1,4 +1,4 @@ -import { getTmdbIdFromImdbId } from './tmdb'; +import { getImdbIdFromTmdbId, getTmdbIdFromImdbId } from './tmdb'; import { Fetcher } from './Fetcher'; import { Context } from '../types'; jest.mock('../utils/Fetcher'); @@ -9,26 +9,40 @@ const ctx: Context = { id: 'id', ip: '127.0.0.1', config: { de: 'on' } }; describe('getTmdbIdFromImdbId', () => { test('series', async () => { - const tmdbId = await getTmdbIdFromImdbId(ctx, fetcher, { id: 'tt2085059', series: 2, episode: 4 }); - - expect(tmdbId).toStrictEqual({ - id: 42009, - series: 2, - episode: 4, - }); + expect(await getTmdbIdFromImdbId(ctx, fetcher, { id: 'tt2085059', series: 2, episode: 4 })) + .toStrictEqual({ id: 42009, series: 2, episode: 4 }); + expect(await getTmdbIdFromImdbId(ctx, fetcher, { id: 'tt2085059', series: 2, episode: 4 })) + .toStrictEqual({ id: 42009, series: 2, episode: 4 }); // from cache }); test('movie', async () => { - const tmdbId = await getTmdbIdFromImdbId(ctx, fetcher, { id: 'tt29141112', series: undefined, episode: undefined }); - - expect(tmdbId).toStrictEqual({ - id: 931944, - series: undefined, - episode: undefined, - }); + expect(await getTmdbIdFromImdbId(ctx, fetcher, { id: 'tt29141112', series: undefined, episode: undefined })) + .toStrictEqual({ id: 931944, series: undefined, episode: undefined }); + expect(await getTmdbIdFromImdbId(ctx, fetcher, { id: 'tt29141112', series: undefined, episode: undefined })) + .toStrictEqual({ id: 931944, series: undefined, episode: undefined }); // from cache }); test('throws with invalid imdb id', async () => { await expect(getTmdbIdFromImdbId(ctx, fetcher, { id: 'tt12345678', series: 1, episode: 1 })).rejects.toThrow('Could not get TMDB ID of IMDb ID "tt12345678"'); }); }); + +describe('getImdbIdFromTmdbId', () => { + test('series', async () => { + expect(await getImdbIdFromTmdbId(ctx, fetcher, { id: 42009, series: 2, episode: 4 })) + .toStrictEqual({ id: 'tt2085059', series: 2, episode: 4 }); + expect(await getImdbIdFromTmdbId(ctx, fetcher, { id: 42009, series: 2, episode: 4 })) + .toStrictEqual({ id: 'tt2085059', series: 2, episode: 4 }); // from cache + }); + + test('movie', async () => { + expect(await getImdbIdFromTmdbId(ctx, fetcher, { id: 931944, series: undefined, episode: undefined })) + .toStrictEqual({ id: 'tt29141112', series: undefined, episode: undefined }); + expect(await getImdbIdFromTmdbId(ctx, fetcher, { id: 931944, series: undefined, episode: undefined })) + .toStrictEqual({ id: 'tt29141112', series: undefined, episode: undefined }); // from cache + }); + + test('throws with invalid tmdb id', async () => { + await expect(getImdbIdFromTmdbId(ctx, fetcher, { id: 12345678, series: 1, episode: 1 })).rejects.toThrow(Error); + }); +}); diff --git a/src/utils/tmdb.ts b/src/utils/tmdb.ts index e6aa518..f3d14f7 100644 --- a/src/utils/tmdb.ts +++ b/src/utils/tmdb.ts @@ -5,10 +5,32 @@ import { envGet } from './env'; export interface TmdbId { id: number; series: number | undefined; episode: number | undefined } -export const getTmdbIdFromImdbId = async (ctx: Context, fetcher: Fetcher, imdbId: ImdbId): Promise => { - const url = new URL(`https://api.themoviedb.org/3/find/${imdbId.id}?external_source=imdb_id`); +interface FindResponsePartial { + movie_results: { + id: number; + }[]; + tv_results: { + id: number; + }[]; +} + +interface ExternalIdsResponsePartial { + imdb_id: string; +} + +const fetch = async (ctx: Context, fetcher: Fetcher, url: URL): Promise => { const config = { 'headers': { Authorization: 'Bearer ' + envGet('TMDB_ACCESS_TOKEN') }, 'Content-Type': 'application/json' }; - const response = JSON.parse(await fetcher.text(ctx, url, config)); + + return JSON.parse(await fetcher.text(ctx, url, config)); +}; + +const imdbTmdbMap = new Map(); +export const getTmdbIdFromImdbId = async (ctx: Context, fetcher: Fetcher, imdbId: ImdbId): Promise => { + if (imdbTmdbMap.has(imdbId.id)) { + 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 id = (imdbId.series ? response.tv_results[0] : response.movie_results[0])?.id; @@ -16,5 +38,20 @@ export const getTmdbIdFromImdbId = async (ctx: Context, fetcher: Fetcher, imdbId throw new Error(`Could not get TMDB ID of IMDb ID "${imdbId.id}"`); } + imdbTmdbMap.set(imdbId.id, id); return { id, series: imdbId.series, episode: imdbId.episode }; }; + +const tmdbImdbMap = new Map(); +export const getImdbIdFromTmdbId = async (ctx: Context, fetcher: Fetcher, tmdbId: TmdbId): Promise => { + if (tmdbImdbMap.has(tmdbId.id)) { + return { id: tmdbImdbMap.get(tmdbId.id) as string, series: tmdbId.series, episode: tmdbId.episode }; + } + + 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; + + tmdbImdbMap.set(tmdbId.id, response.imdb_id); + return { id: response.imdb_id, series: tmdbId.series, episode: tmdbId.episode }; +};