import { parsePartialDate } from "@sttracker/plugin-sdk/partial-date"; import type { MediaType, SourceRecord, WorkHint } from "@sttracker/plugin-sdk/types"; interface Ids { trakt?: number; slug?: string; imdb?: string | null; tmdb?: number | null; tvdb?: number | null } interface TMovie { title: string; year?: number | null; ids: Ids } interface TShow { title: string; year?: number | null; ids: Ids } interface TSeason { number: number; ids?: Ids } interface TEpisode { season: number; number: number; title?: string | null; ids?: Ids } export interface TItem { type: string; id?: number; watched_at?: string; rating?: number; notes?: string | null; movie?: TMovie; show?: TShow; season?: TSeason; episode?: TEpisode; } function ids(map: Record): Record { const out: Record = {}; for (const [k, v] of Object.entries(map)) if (v !== undefined && v !== null && v !== "") out[k] = String(v); return out; } function movieHint(m: TMovie): WorkHint { return { title: m.title, mediaType: "movie", ...(m.year ? { year: m.year } : {}), externalIds: ids({ trakt_movie: m.ids.trakt, tmdb_movie: m.ids.tmdb, imdb: m.ids.imdb }), }; } function showHint(s: TShow): WorkHint { return { title: s.title, mediaType: "show", ...(s.year ? { year: s.year } : {}), externalIds: ids({ trakt_show: s.ids.trakt, tmdb_show: s.ids.tmdb, tvdb_show: s.ids.tvdb, imdb: s.ids.imdb }), }; } function seasonHint(show: TShow, n: number, sid?: Ids): WorkHint { return { title: `Season ${n}`, mediaType: "season", externalIds: sid ? ids({ trakt_season: sid.trakt, tmdb_season: sid.tmdb }) : {}, parent: showHint(show), position: { season: n }, }; } function episodeHint(show: TShow, e: TEpisode): WorkHint { const eid = e.ids; return { title: e.title || `Episode ${e.number}`, mediaType: "episode", externalIds: eid ? ids({ trakt_episode: eid.trakt, tvdb_episode: eid.tvdb, tmdb_episode: eid.tmdb, imdb: eid.imdb }) : {}, parent: seasonHint(show, e.season), position: { season: e.season, episode: e.number }, }; } /** Maps a Trakt item (history, rating, watchlist or list item) to a WorkHint, or null if unsupported. */ export function hintFor(item: TItem): WorkHint | null { switch (item.type) { case "movie": return item.movie ? movieHint(item.movie) : null; case "show": return item.show ? showHint(item.show) : null; case "season": return item.show && item.season ? seasonHint(item.show, item.season.number, item.season.ids) : null; case "episode": return item.show && item.episode ? episodeHint(item.show, item.episode) : null; default: return null; } } export function traktIdOf(item: TItem): number | undefined { switch (item.type) { case "movie": return item.movie?.ids.trakt; case "show": return item.show?.ids.trakt; case "season": return item.season?.ids?.trakt; case "episode": return item.episode?.ids?.trakt; default: return undefined; } } export function historyRecord(item: TItem): SourceRecord | null { const work = hintFor(item); if (!work || item.id === undefined) return null; const finishedAt = parsePartialDate(item.watched_at); return { kind: "engagement", key: `history:${item.id}`, raw: item, work, engagement: { finishedAt } }; } export function ratingRecord(item: TItem): SourceRecord | null { const work = hintFor(item); const tid = traktIdOf(item); if (!work || tid === undefined || typeof item.rating !== "number") return null; return { kind: "entry", key: `rating:${item.type}:${tid}`, raw: item, work, entry: { rating: item.rating * 10 } }; } export function watchlistRecord(item: TItem): SourceRecord | null { const work = hintFor(item); const tid = traktIdOf(item); if (!work || tid === undefined) return null; return { kind: "entry", key: `watchlist:${item.type}:${tid}`, raw: item, work, entry: { status: "planned" } }; } export interface TList { name: string; description?: string | null; ids: Ids } export function listRecord(list: TList, items: TItem[]): SourceRecord { const mapped: { work: WorkHint; note?: string }[] = []; for (const it of items) { const work = hintFor(it); if (work) mapped.push({ work, ...(it.notes ? { note: it.notes } : {}) }); } return { kind: "list", key: `list:${list.ids.trakt}`, raw: { list, items }, list: { name: list.name, ...(list.description ? { description: list.description } : {}), items: mapped }, }; } export function searchHint(r: { type: string; movie?: TMovie; show?: TShow }): WorkHint | null { return hintFor({ type: r.type, ...(r.movie ? { movie: r.movie } : {}), ...(r.show ? { show: r.show } : {}) }); } export type { MediaType };