129 lines
4.7 KiB
TypeScript
129 lines
4.7 KiB
TypeScript
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<string, unknown>): Record<string, string> {
|
|
const out: Record<string, string> = {};
|
|
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 };
|