fix(plugins): type-specific trakt external id namespaces
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
59ccc90bc1
commit
9708fb5e70
2 files changed
+26
-10
No files matched your search
@@ -29,7 +29,7 @@ function movieHint(m: TMovie): WorkHint {
|
|||||||
title: m.title,
|
title: m.title,
|
||||||
mediaType: "movie",
|
mediaType: "movie",
|
||||||
...(m.year ? { year: m.year } : {}),
|
...(m.year ? { year: m.year } : {}),
|
||||||
externalIds: ids({ trakt: m.ids.trakt, tmdb_movie: m.ids.tmdb, imdb: m.ids.imdb }),
|
externalIds: ids({ trakt_movie: m.ids.trakt, tmdb_movie: m.ids.tmdb, imdb: m.ids.imdb }),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,21 +38,26 @@ function showHint(s: TShow): WorkHint {
|
|||||||
title: s.title,
|
title: s.title,
|
||||||
mediaType: "show",
|
mediaType: "show",
|
||||||
...(s.year ? { year: s.year } : {}),
|
...(s.year ? { year: s.year } : {}),
|
||||||
externalIds: ids({ tmdb_show: s.ids.tmdb, tvdb: s.ids.tvdb, imdb: s.ids.imdb, trakt: s.ids.trakt }),
|
externalIds: ids({ trakt_show: s.ids.trakt, tmdb_show: s.ids.tmdb, tvdb_show: s.ids.tvdb, imdb: s.ids.imdb }),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Season and episode ids are deliberately omitted: their numeric namespaces (tvdb, tmdb, imdb, trakt)
|
function seasonHint(show: TShow, n: number, sid?: Ids): WorkHint {
|
||||||
// are shared with shows/movies, so they are identified by parent chain + position instead.
|
return {
|
||||||
function seasonHint(show: TShow, n: number): WorkHint {
|
title: `Season ${n}`,
|
||||||
return { title: `Season ${n}`, mediaType: "season", externalIds: {}, parent: showHint(show), position: { 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 {
|
function episodeHint(show: TShow, e: TEpisode): WorkHint {
|
||||||
|
const eid = e.ids;
|
||||||
return {
|
return {
|
||||||
title: e.title || `Episode ${e.number}`,
|
title: e.title || `Episode ${e.number}`,
|
||||||
mediaType: "episode",
|
mediaType: "episode",
|
||||||
externalIds: {},
|
externalIds: eid ? ids({ trakt_episode: eid.trakt, tvdb_episode: eid.tvdb, tmdb_episode: eid.tmdb, imdb: eid.imdb }) : {},
|
||||||
parent: seasonHint(show, e.season),
|
parent: seasonHint(show, e.season),
|
||||||
position: { season: e.season, episode: e.number },
|
position: { season: e.season, episode: e.number },
|
||||||
};
|
};
|
||||||
@@ -63,7 +68,7 @@ export function hintFor(item: TItem): WorkHint | null {
|
|||||||
switch (item.type) {
|
switch (item.type) {
|
||||||
case "movie": return item.movie ? movieHint(item.movie) : null;
|
case "movie": return item.movie ? movieHint(item.movie) : null;
|
||||||
case "show": return item.show ? showHint(item.show) : null;
|
case "show": return item.show ? showHint(item.show) : null;
|
||||||
case "season": return item.show && item.season ? seasonHint(item.show, item.season.number) : 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;
|
case "episode": return item.show && item.episode ? episodeHint(item.show, item.episode) : null;
|
||||||
default: return null;
|
default: return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ describe("trakt pull", () => {
|
|||||||
const r = (await collect(plugin.pull!(ctx))).find((x) => x.key === "history:9001")!;
|
const r = (await collect(plugin.pull!(ctx))).find((x) => x.key === "history:9001")!;
|
||||||
if (r.kind !== "engagement") throw new Error("kind");
|
if (r.kind !== "engagement") throw new Error("kind");
|
||||||
expect(r.work.mediaType).toBe("movie");
|
expect(r.work.mediaType).toBe("movie");
|
||||||
expect(r.work.externalIds).toEqual({ trakt: "1001", tmdb_movie: "329865", imdb: "tt2543164" });
|
expect(r.work.externalIds).toEqual({ trakt_movie: "1001", tmdb_movie: "329865", imdb: "tt2543164" });
|
||||||
expect(r.engagement.finishedAt).toEqual({ value: "2024-03-05T20:15:00.000Z", precision: "day" });
|
expect(r.engagement.finishedAt).toEqual({ value: "2024-03-05T20:15:00.000Z", precision: "day" });
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -71,11 +71,12 @@ describe("trakt pull", () => {
|
|||||||
if (r.kind !== "engagement") throw new Error("kind");
|
if (r.kind !== "engagement") throw new Error("kind");
|
||||||
expect(r.work.mediaType).toBe("episode");
|
expect(r.work.mediaType).toBe("episode");
|
||||||
expect(r.work.position).toEqual({ season: 2, episode: 3 });
|
expect(r.work.position).toEqual({ season: 2, episode: 3 });
|
||||||
|
expect(r.work.externalIds).toEqual({ trakt_episode: "5003", tvdb_episode: "7003", tmdb_episode: "8003", imdb: "tt0000003" });
|
||||||
expect(r.work.parent?.mediaType).toBe("season");
|
expect(r.work.parent?.mediaType).toBe("season");
|
||||||
expect(r.work.parent?.position).toEqual({ season: 2 });
|
expect(r.work.parent?.position).toEqual({ season: 2 });
|
||||||
const show = r.work.parent?.parent;
|
const show = r.work.parent?.parent;
|
||||||
expect(show?.mediaType).toBe("show");
|
expect(show?.mediaType).toBe("show");
|
||||||
expect(show?.externalIds).toEqual({ tmdb_show: "80002", tvdb: "70002", imdb: "tt7000002", trakt: "2002" });
|
expect(show?.externalIds).toEqual({ trakt_show: "2002", tmdb_show: "80002", tvdb_show: "70002", imdb: "tt7000002" });
|
||||||
});
|
});
|
||||||
|
|
||||||
test("rating 8 becomes 80", async () => {
|
test("rating 8 becomes 80", async () => {
|
||||||
@@ -132,6 +133,16 @@ describe("trakt pull", () => {
|
|||||||
expect(fx.calls[1]!.headers.get("authorization")).toBe("Bearer new-access");
|
expect(fx.calls[1]!.headers.get("authorization")).toBe("Bearer new-access");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("a failed refresh saves nothing and never calls /sync", async () => {
|
||||||
|
const { fx, ctx } = await setup(
|
||||||
|
{ ...fullRoutes, [`POST ${B}/oauth/token`]: () => new Response("{}", { status: 400 }) },
|
||||||
|
{ ...FRESH, expiresAt: "2024-04-01T12:00:00.000Z" },
|
||||||
|
);
|
||||||
|
await expect(collect(plugin.pull!(ctx))).rejects.toThrow("400");
|
||||||
|
expect(ctx.saved.credentials).toEqual([]);
|
||||||
|
expect(fx.calls.map((c) => new URL(c.url).pathname)).toEqual(["/oauth/token"]);
|
||||||
|
});
|
||||||
|
|
||||||
test("a token with more than 24h left is not refreshed", async () => {
|
test("a token with more than 24h left is not refreshed", async () => {
|
||||||
const { fx, ctx } = await setup(fullRoutes);
|
const { fx, ctx } = await setup(fullRoutes);
|
||||||
await collect(plugin.pull!(ctx));
|
await collect(plugin.pull!(ctx));
|
||||||
|
|||||||
Reference in new issue
Block a user