Files
STTracker/plugins/trakt/src/map.ts
T

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 };