93 lines
3.7 KiB
TypeScript
93 lines
3.7 KiB
TypeScript
import type { PluginContext, SourceRecord } from "@sttracker/plugin-sdk/types";
|
|
import { ensureAccessToken, get } from "./api.ts";
|
|
import { historyRecord, listRecord, ratingRecord, watchlistRecord, type TItem, type TList } from "./map.ts";
|
|
|
|
const PAGE_LIMIT = 250;
|
|
|
|
async function* history(ctx: PluginContext, token: string, startAt?: string): AsyncGenerator<SourceRecord> {
|
|
for (let page = 1, pages = 1; page <= pages; page++) {
|
|
const r = await get<TItem[]>(ctx, token, "/sync/history", { page, limit: PAGE_LIMIT, start_at: startAt });
|
|
pages = r.pageCount;
|
|
for (const item of r.data) {
|
|
const rec = historyRecord(item);
|
|
if (rec) yield rec;
|
|
}
|
|
}
|
|
}
|
|
|
|
async function* ratings(ctx: PluginContext, token: string): AsyncGenerator<SourceRecord> {
|
|
for (const item of (await get<TItem[]>(ctx, token, "/sync/ratings")).data) {
|
|
const rec = ratingRecord(item);
|
|
if (rec) yield rec;
|
|
}
|
|
}
|
|
|
|
async function* watchlist(ctx: PluginContext, token: string): AsyncGenerator<SourceRecord> {
|
|
for (const item of (await get<TItem[]>(ctx, token, "/sync/watchlist")).data) {
|
|
const rec = watchlistRecord(item);
|
|
if (rec) yield rec;
|
|
}
|
|
}
|
|
|
|
async function* lists(ctx: PluginContext, token: string): AsyncGenerator<SourceRecord> {
|
|
for (const list of (await get<TList[]>(ctx, token, "/users/me/lists")).data) {
|
|
const items = (await get<TItem[]>(ctx, token, `/users/me/lists/${list.ids.trakt}/items`)).data;
|
|
yield listRecord(list, items);
|
|
}
|
|
}
|
|
|
|
export async function* pull(ctx: PluginContext): AsyncGenerator<SourceRecord> {
|
|
const token = await ensureAccessToken(ctx);
|
|
yield* history(ctx, token);
|
|
yield* ratings(ctx, token);
|
|
yield* watchlist(ctx, token);
|
|
yield* lists(ctx, token);
|
|
}
|
|
|
|
interface Activity { watched_at?: string | null; rated_at?: string | null; watchlisted_at?: string | null; updated_at?: string | null }
|
|
interface LastActivities { movies?: Activity; episodes?: Activity; shows?: Activity; seasons?: Activity; lists?: Activity }
|
|
interface Cursor { history: string; ratings: string; watchlist: string; lists: string }
|
|
|
|
const maxIso = (xs: (string | null | undefined)[]): string =>
|
|
xs.reduce<string>((a, x) => (x && x > a ? x : a), "");
|
|
|
|
function cursorFrom(a: LastActivities): Cursor {
|
|
return {
|
|
history: maxIso([a.movies?.watched_at, a.episodes?.watched_at]),
|
|
ratings: maxIso([a.movies?.rated_at, a.episodes?.rated_at, a.shows?.rated_at, a.seasons?.rated_at]),
|
|
watchlist: maxIso([a.movies?.watchlisted_at, a.episodes?.watchlisted_at, a.shows?.watchlisted_at, a.seasons?.watchlisted_at]),
|
|
lists: maxIso([a.lists?.updated_at]),
|
|
};
|
|
}
|
|
|
|
function parseCursor(s: string | null): Cursor | null {
|
|
if (!s) return null;
|
|
try {
|
|
const c = JSON.parse(s) as Partial<Cursor>;
|
|
return { history: c.history ?? "", ratings: c.ratings ?? "", watchlist: c.watchlist ?? "", lists: c.lists ?? "" };
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export async function* pullChanges(ctx: PluginContext): AsyncGenerator<SourceRecord> {
|
|
const token = await ensureAccessToken(ctx);
|
|
const raw = await ctx.cursor.get();
|
|
const prev = parseCursor(raw);
|
|
const next = cursorFrom((await get<LastActivities>(ctx, token, "/sync/last_activities")).data);
|
|
if (!prev) {
|
|
// No usable cursor: full pull, then remember where we are.
|
|
yield* history(ctx, token);
|
|
yield* ratings(ctx, token);
|
|
yield* watchlist(ctx, token);
|
|
yield* lists(ctx, token);
|
|
} else {
|
|
if (next.history !== prev.history) yield* history(ctx, token, prev.history || undefined);
|
|
if (next.ratings !== prev.ratings) yield* ratings(ctx, token);
|
|
if (next.watchlist !== prev.watchlist) yield* watchlist(ctx, token);
|
|
if (next.lists !== prev.lists) yield* lists(ctx, token);
|
|
}
|
|
const encoded = JSON.stringify(next);
|
|
if (encoded !== raw) await ctx.cursor.set(encoded);
|
|
}
|