diff --git a/packages/obsidian/src/watchlist/tmdb.ts b/packages/obsidian/src/watchlist/tmdb.ts new file mode 100644 index 0000000..37af481 --- /dev/null +++ b/packages/obsidian/src/watchlist/tmdb.ts @@ -0,0 +1,30 @@ +export type HttpJsonFn = (url: string, headers: Record) => Promise; + +const BASE = 'https://api.themoviedb.org/3'; + +function authParts(key: string): { headers: Record; extraParams: Record } { + if (key.startsWith('eyJ')) { + return { headers: { Authorization: `Bearer ${key}`, accept: 'application/json' }, extraParams: {} }; + } + return { headers: { accept: 'application/json' }, extraParams: { api_key: key } }; +} + +function buildUrl(path: string, params: Record): string { + const qs = new URLSearchParams(params); + return `${BASE}${path}?${qs.toString()}`; +} + +export async function fetchDetail(http: HttpJsonFn, key: string, tmdbId: string, isMovie: boolean): Promise { + const { headers, extraParams } = authParts(key); + const path = isMovie ? `/movie/${tmdbId}` : `/tv/${tmdbId}`; + const append = isMovie ? 'credits,external_ids,release_dates,videos' : 'aggregate_credits,external_ids,content_ratings,videos'; + return await http(buildUrl(path, { append_to_response: append, language: 'en-US', ...extraParams }), headers); +} + +export async function searchTitle(http: HttpJsonFn, key: string, query: string, isMovie: boolean, year?: string): Promise { + const { headers, extraParams } = authParts(key); + const params: Record = { query, language: 'en-US', ...extraParams }; + if (year) params[isMovie ? 'primary_release_year' : 'first_air_date_year'] = year; + const res = await http(buildUrl(isMovie ? '/search/movie' : '/search/tv', params), headers); + return res?.results ?? []; +} diff --git a/tests/watchlist-tmdb.test.ts b/tests/watchlist-tmdb.test.ts new file mode 100644 index 0000000..21777a0 --- /dev/null +++ b/tests/watchlist-tmdb.test.ts @@ -0,0 +1,57 @@ +import { describe, expect, test } from 'bun:test'; +import { fetchDetail, searchTitle } from 'packages/obsidian/src/watchlist/tmdb'; + +function capture(): { calls: { url: string; headers: Record }[]; http: any } { + const calls: { url: string; headers: Record }[] = []; + return { + calls, + http: async (url: string, headers: Record) => { + calls.push({ url, headers }); + return { results: [] }; + }, + }; +} + +describe('fetchDetail', () => { + test('v4 token → Bearer header, no api_key param', async () => { + const { calls, http } = capture(); + await fetchDetail(http, 'eyJhbGciOi.fake.jwt', '693134', true); + expect(calls[0].headers['Authorization']).toBe('Bearer eyJhbGciOi.fake.jwt'); + expect(calls[0].url).not.toContain('api_key'); + }); + test('v3 key → api_key param, no auth header', async () => { + const { calls, http } = capture(); + await fetchDetail(http, 'abc123', '693134', true); + expect(calls[0].url).toContain('api_key=abc123'); + expect(calls[0].headers['Authorization']).toBeUndefined(); + }); + test('movie url + append', async () => { + const { calls, http } = capture(); + await fetchDetail(http, 'k', '693134', true); + expect(calls[0].url).toContain('/3/movie/693134'); + expect(calls[0].url).toContain('append_to_response=credits%2Cexternal_ids%2Crelease_dates%2Cvideos'); + expect(calls[0].url).toContain('language=en-US'); + }); + test('tv url + append', async () => { + const { calls, http } = capture(); + await fetchDetail(http, 'k', '84958', false); + expect(calls[0].url).toContain('/3/tv/84958'); + expect(calls[0].url).toContain('append_to_response=aggregate_credits%2Cexternal_ids%2Ccontent_ratings%2Cvideos'); + }); +}); + +describe('searchTitle', () => { + test('movie search url + year', async () => { + const { calls, http } = capture(); + await searchTitle(http, 'k', 'Dune Part Two', true, '2024'); + expect(calls[0].url).toContain('/3/search/movie'); + expect(calls[0].url).toContain('query=Dune+Part+Two'); + expect(calls[0].url).toContain('primary_release_year=2024'); + }); + test('tv search url', async () => { + const { calls, http } = capture(); + await searchTitle(http, 'k', 'Loki', false, '2021'); + expect(calls[0].url).toContain('/3/search/tv'); + expect(calls[0].url).toContain('first_air_date_year=2021'); + }); +});