feat(plugins): trakt
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
06a59e1b5c
commit
59ccc90bc1
@@ -0,0 +1,92 @@
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user