56 lines
2.3 KiB
TypeScript
56 lines
2.3 KiB
TypeScript
import { HttpError } from "@sttracker/plugin-sdk/http";
|
|
import { definePlugin } from "@sttracker/plugin-sdk/types";
|
|
import { BASE, clientCreds, post, toCredentials, type TokenResponse } from "./src/api.ts";
|
|
import { searchHint } from "./src/map.ts";
|
|
import { pull, pullChanges } from "./src/sync.ts";
|
|
|
|
interface DeviceCode { device_code: string; user_code: string; verification_url: string; expires_in: number; interval: number }
|
|
|
|
export default definePlugin({
|
|
manifest: {
|
|
id: "trakt",
|
|
name: "Trakt",
|
|
version: "0.1.0",
|
|
sdkVersion: 1,
|
|
mediaTypes: ["movie", "show", "season", "episode"],
|
|
capabilities: ["pull", "pullChanges", "lookup"],
|
|
config: [
|
|
{ key: "clientId", label: "Client ID", type: "string", required: true },
|
|
{ key: "clientSecret", label: "Client secret", type: "secret", required: true },
|
|
],
|
|
attribution: "STTracker uses Trakt but is not endorsed or certified by Trakt.",
|
|
},
|
|
deviceAuth: {
|
|
async start(ctx) {
|
|
const { clientId } = clientCreds(ctx);
|
|
const d = await post<DeviceCode>(ctx, "/oauth/device/code", { client_id: clientId });
|
|
return { userCode: d.user_code, verificationUrl: d.verification_url, deviceCode: d.device_code, intervalSec: d.interval, expiresInSec: d.expires_in };
|
|
},
|
|
async poll(ctx, deviceCode) {
|
|
const { clientId, clientSecret } = clientCreds(ctx);
|
|
try {
|
|
const t = await post<TokenResponse>(ctx, "/oauth/device/token", { code: deviceCode, client_id: clientId, client_secret: clientSecret });
|
|
return toCredentials(t, ctx.now());
|
|
} catch (e) {
|
|
// 400 = pending, 429 = polling too fast; anything else (404/409/410/418) is terminal.
|
|
if (e instanceof HttpError && (e.status === 400 || e.status === 429)) return "pending";
|
|
throw e;
|
|
}
|
|
},
|
|
},
|
|
pull,
|
|
pullChanges,
|
|
async lookup(ctx, q) {
|
|
const { clientId } = clientCreds(ctx);
|
|
const type = q.mediaType === "movie" || q.mediaType === "show" ? q.mediaType : "movie,show";
|
|
const res = await ctx.http.json<Parameters<typeof searchHint>[0][]>(`${BASE}/search/${type}`, {
|
|
headers: { "trakt-api-version": "2", "trakt-api-key": clientId },
|
|
query: { query: q.title, years: q.year },
|
|
});
|
|
return res.flatMap((r) => {
|
|
const h = searchHint(r);
|
|
return h ? [h] : [];
|
|
});
|
|
},
|
|
});
|