mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-08-29 02:45:39 -04:00
Replace hand-written TMDB types with openapi-fetch + generated schema
- Add `openapi-fetch` dependency; generate `lib/tmdb/schema.d.ts` from TMDB's OpenAPI spec and delete `lib/tmdb/types.ts` - Rewrite `lib/tmdb/client.ts` to use the typed fetch client against the generated schema, exporting `TmdbMovieDetails`, `TmdbTvDetails`, `TmdbVideo`, and `TmdbGenre` directly - Fix null-safety across services (`credits`, `availability`, `metadata`, `person`, `webhooks`) and call sites (explore page, search route, explore actions) to handle optional fields produced by the stricter generated types - Update `metadata.ts` and `metadata.test.ts` to import shared types from `@/lib/tmdb/client` instead of the removed `types.ts` - Exclude `lib/tmdb/schema.d.ts` from Biome linting
This commit is contained in:
@@ -30,8 +30,8 @@ export async function refreshAvailability(titleId: string) {
|
||||
titleId,
|
||||
region: "US",
|
||||
providerId: p.provider_id,
|
||||
providerName: p.provider_name,
|
||||
logoPath: p.logo_path,
|
||||
providerName: p.provider_name ?? "",
|
||||
logoPath: p.logo_path ?? "",
|
||||
offerType,
|
||||
link: us.link ?? null,
|
||||
lastFetchedAt: now,
|
||||
|
||||
+20
-18
@@ -95,13 +95,13 @@ export async function refreshCredits(
|
||||
|
||||
if (title.type === "movie") {
|
||||
const credits = await getMovieCredits(title.tmdbId);
|
||||
const castSlice = credits.cast.slice(0, 20);
|
||||
const castSlice = (credits.cast ?? []).slice(0, 20);
|
||||
|
||||
// Collect notable crew
|
||||
const seenCrew = new Set<string>();
|
||||
const notableCrew: typeof credits.crew = [];
|
||||
for (const c of credits.crew) {
|
||||
if (!NOTABLE_DEPARTMENTS.has(c.job)) continue;
|
||||
for (const c of credits.crew ?? []) {
|
||||
if (!NOTABLE_DEPARTMENTS.has(c.job ?? "")) continue;
|
||||
const key = `${c.id}-${c.job}`;
|
||||
if (seenCrew.has(key)) continue;
|
||||
seenCrew.add(key);
|
||||
@@ -112,14 +112,14 @@ export async function refreshCredits(
|
||||
const allPeople: PersonData[] = [
|
||||
...castSlice.map((c) => ({
|
||||
tmdbId: c.id,
|
||||
name: c.name,
|
||||
profilePath: c.profile_path,
|
||||
name: c.name ?? "",
|
||||
profilePath: c.profile_path ?? null,
|
||||
popularity: c.popularity,
|
||||
})),
|
||||
...notableCrew.map((c) => ({
|
||||
tmdbId: c.id,
|
||||
name: c.name,
|
||||
profilePath: c.profile_path,
|
||||
name: c.name ?? "",
|
||||
profilePath: c.profile_path ?? null,
|
||||
popularity: c.popularity,
|
||||
})),
|
||||
];
|
||||
@@ -180,24 +180,26 @@ export async function refreshCredits(
|
||||
}
|
||||
} else {
|
||||
const credits = await getTvAggregateCredits(title.tmdbId);
|
||||
const castSlice = credits.cast.slice(0, 20);
|
||||
const tvCast = credits.cast ?? [];
|
||||
const tvCrew = credits.crew ?? [];
|
||||
const castSlice = tvCast.slice(0, 20);
|
||||
|
||||
// Collect notable crew
|
||||
const seenCrew = new Set<string>();
|
||||
const notableCrew: Array<{
|
||||
person: (typeof credits.crew)[0];
|
||||
person: (typeof tvCrew)[number];
|
||||
job: string;
|
||||
episodeCount: number;
|
||||
}> = [];
|
||||
for (const c of credits.crew) {
|
||||
for (const j of c.jobs) {
|
||||
if (!NOTABLE_DEPARTMENTS.has(j.job)) continue;
|
||||
for (const c of tvCrew) {
|
||||
for (const j of c.jobs ?? []) {
|
||||
if (!NOTABLE_DEPARTMENTS.has(j.job ?? "")) continue;
|
||||
const key = `${c.id}-${j.job}`;
|
||||
if (seenCrew.has(key)) continue;
|
||||
seenCrew.add(key);
|
||||
notableCrew.push({
|
||||
person: c,
|
||||
job: j.job,
|
||||
job: j.job ?? "",
|
||||
episodeCount: j.episode_count,
|
||||
});
|
||||
}
|
||||
@@ -207,14 +209,14 @@ export async function refreshCredits(
|
||||
const allPeople: PersonData[] = [
|
||||
...castSlice.map((c) => ({
|
||||
tmdbId: c.id,
|
||||
name: c.name,
|
||||
profilePath: c.profile_path,
|
||||
name: c.name ?? "",
|
||||
profilePath: c.profile_path ?? null,
|
||||
popularity: c.popularity,
|
||||
})),
|
||||
...notableCrew.map((c) => ({
|
||||
tmdbId: c.person.id,
|
||||
name: c.person.name,
|
||||
profilePath: c.person.profile_path,
|
||||
name: c.person.name ?? "",
|
||||
profilePath: c.person.profile_path ?? null,
|
||||
popularity: c.person.popularity,
|
||||
})),
|
||||
];
|
||||
@@ -247,7 +249,7 @@ export async function refreshCredits(
|
||||
titleId,
|
||||
personId,
|
||||
character: null,
|
||||
department: c.person.department,
|
||||
department: c.person.department ?? "",
|
||||
job: c.job,
|
||||
displayOrder: crewOrder,
|
||||
episodeCount: c.episodeCount,
|
||||
|
||||
@@ -3,7 +3,7 @@ import type {
|
||||
TmdbMovieDetails,
|
||||
TmdbTvDetails,
|
||||
TmdbVideo,
|
||||
} from "@/lib/tmdb/types";
|
||||
} from "@/lib/tmdb/client";
|
||||
import {
|
||||
extractMovieContentRating,
|
||||
extractTvContentRating,
|
||||
|
||||
+42
-35
@@ -12,6 +12,12 @@ import {
|
||||
} from "@/lib/db/schema";
|
||||
import { createLogger } from "@/lib/logger";
|
||||
import { generateProviderUrl } from "@/lib/providers";
|
||||
import type {
|
||||
TmdbGenre,
|
||||
TmdbMovieDetails,
|
||||
TmdbTvDetails,
|
||||
TmdbVideo,
|
||||
} from "@/lib/tmdb/client";
|
||||
import {
|
||||
getMovieDetails,
|
||||
getRecommendations,
|
||||
@@ -21,12 +27,6 @@ import {
|
||||
getVideos,
|
||||
} from "@/lib/tmdb/client";
|
||||
import { tmdbImageUrl } from "@/lib/tmdb/image";
|
||||
import type {
|
||||
TmdbGenre,
|
||||
TmdbMovieDetails,
|
||||
TmdbTvDetails,
|
||||
TmdbVideo,
|
||||
} from "@/lib/tmdb/types";
|
||||
import type {
|
||||
AvailabilityOffer,
|
||||
CastMember,
|
||||
@@ -66,16 +66,19 @@ function upsertTitle(values: typeof titles.$inferInsert, tmdbId: number) {
|
||||
}
|
||||
|
||||
function upsertGenres(titleId: string, tmdbGenres: TmdbGenre[]) {
|
||||
if (tmdbGenres.length === 0) return;
|
||||
const validGenres = tmdbGenres.filter(
|
||||
(g): g is TmdbGenre & { name: string } => !!g.name,
|
||||
);
|
||||
if (validGenres.length === 0) return;
|
||||
db.transaction((tx) => {
|
||||
for (const g of tmdbGenres) {
|
||||
for (const g of validGenres) {
|
||||
tx.insert(genres)
|
||||
.values({ id: g.id, name: g.name })
|
||||
.onConflictDoUpdate({ target: genres.id, set: { name: g.name } })
|
||||
.run();
|
||||
}
|
||||
tx.delete(titleGenres).where(eq(titleGenres.titleId, titleId)).run();
|
||||
for (const g of tmdbGenres) {
|
||||
for (const g of validGenres) {
|
||||
tx.insert(titleGenres)
|
||||
.values({ titleId, genreId: g.id })
|
||||
.onConflictDoNothing()
|
||||
@@ -90,7 +93,7 @@ export function extractMovieContentRating(
|
||||
): string | null {
|
||||
const us = movie.release_dates?.results?.find((r) => r.iso_3166_1 === "US");
|
||||
if (!us) return null;
|
||||
for (const rd of us.release_dates) {
|
||||
for (const rd of us.release_dates ?? []) {
|
||||
if (rd.certification) return rd.certification;
|
||||
}
|
||||
return null;
|
||||
@@ -159,7 +162,7 @@ async function _getOrFetchTitleByTmdbId(tmdbId: number, type: "movie" | "tv") {
|
||||
.where(eq(titles.id, existing.id))
|
||||
.run();
|
||||
}
|
||||
upsertGenres(existing.id, show.genres);
|
||||
upsertGenres(existing.id, show.genres ?? []);
|
||||
await refreshTvChildren(existing.id, tmdbId, show.number_of_seasons);
|
||||
refreshAvailability(existing.id).catch((err) =>
|
||||
log.debug("Availability enrichment failed:", err),
|
||||
@@ -167,8 +170,8 @@ async function _getOrFetchTitleByTmdbId(tmdbId: number, type: "movie" | "tv") {
|
||||
refreshRecommendations(existing.id).catch((err) =>
|
||||
log.debug("Recommendations enrichment failed:", err),
|
||||
);
|
||||
extractAndStoreColors(existing.id, show.poster_path).catch((err) =>
|
||||
log.debug("Color extraction failed:", err),
|
||||
extractAndStoreColors(existing.id, show.poster_path ?? null).catch(
|
||||
(err) => log.debug("Color extraction failed:", err),
|
||||
);
|
||||
refreshCredits(existing.id).catch((err) =>
|
||||
log.debug("Credits enrichment failed:", err),
|
||||
@@ -198,7 +201,7 @@ async function _getOrFetchTitleByTmdbId(tmdbId: number, type: "movie" | "tv") {
|
||||
{
|
||||
tmdbId: movie.id,
|
||||
type: "movie",
|
||||
title: movie.title,
|
||||
title: movie.title ?? "",
|
||||
originalTitle: movie.original_title,
|
||||
overview: movie.overview,
|
||||
releaseDate: movie.release_date || null,
|
||||
@@ -214,14 +217,14 @@ async function _getOrFetchTitleByTmdbId(tmdbId: number, type: "movie" | "tv") {
|
||||
tmdbId,
|
||||
);
|
||||
if (!row) return undefined;
|
||||
upsertGenres(row.id, movie.genres);
|
||||
upsertGenres(row.id, movie.genres ?? []);
|
||||
refreshAvailability(row.id).catch((err) =>
|
||||
log.debug("Availability enrichment failed:", err),
|
||||
);
|
||||
refreshRecommendations(row.id).catch((err) =>
|
||||
log.debug("Recommendations enrichment failed:", err),
|
||||
);
|
||||
extractAndStoreColors(row.id, movie.poster_path).catch((err) =>
|
||||
extractAndStoreColors(row.id, movie.poster_path ?? null).catch((err) =>
|
||||
log.debug("Color extraction failed:", err),
|
||||
);
|
||||
refreshCredits(row.id).catch((err) =>
|
||||
@@ -245,7 +248,7 @@ async function _getOrFetchTitleByTmdbId(tmdbId: number, type: "movie" | "tv") {
|
||||
tmdbId: show.id,
|
||||
tvdbId: show.external_ids?.tvdb_id ?? null,
|
||||
type: "tv",
|
||||
title: show.name,
|
||||
title: show.name ?? "",
|
||||
originalTitle: show.original_name,
|
||||
overview: show.overview,
|
||||
firstAirDate: show.first_air_date || null,
|
||||
@@ -261,7 +264,7 @@ async function _getOrFetchTitleByTmdbId(tmdbId: number, type: "movie" | "tv") {
|
||||
tmdbId,
|
||||
);
|
||||
if (!row) return undefined;
|
||||
upsertGenres(row.id, show.genres);
|
||||
upsertGenres(row.id, show.genres ?? []);
|
||||
|
||||
await refreshTvChildren(row.id, tmdbId, show.number_of_seasons);
|
||||
refreshAvailability(row.id).catch((err) =>
|
||||
@@ -270,7 +273,7 @@ async function _getOrFetchTitleByTmdbId(tmdbId: number, type: "movie" | "tv") {
|
||||
refreshRecommendations(row.id).catch((err) =>
|
||||
log.debug("Recommendations enrichment failed:", err),
|
||||
);
|
||||
extractAndStoreColors(row.id, show.poster_path).catch((err) =>
|
||||
extractAndStoreColors(row.id, show.poster_path ?? null).catch((err) =>
|
||||
log.debug("Color extraction failed:", err),
|
||||
);
|
||||
refreshCredits(row.id).catch((err) =>
|
||||
@@ -316,7 +319,7 @@ export async function refreshTitle(titleId: string) {
|
||||
})
|
||||
.where(eq(titles.id, titleId))
|
||||
.run();
|
||||
upsertGenres(titleId, movie.genres);
|
||||
upsertGenres(titleId, movie.genres ?? []);
|
||||
} else {
|
||||
const show = await getTvDetails(title.tmdbId);
|
||||
db.update(titles)
|
||||
@@ -337,7 +340,7 @@ export async function refreshTitle(titleId: string) {
|
||||
})
|
||||
.where(eq(titles.id, titleId))
|
||||
.run();
|
||||
upsertGenres(titleId, show.genres);
|
||||
upsertGenres(titleId, show.genres ?? []);
|
||||
await refreshTvChildren(titleId, title.tmdbId, show.number_of_seasons);
|
||||
}
|
||||
|
||||
@@ -405,9 +408,10 @@ export async function refreshTvChildren(
|
||||
.get();
|
||||
|
||||
// Batch all episode upserts in a single transaction per season
|
||||
if (seasonData.episodes.length > 0) {
|
||||
const eps = seasonData.episodes ?? [];
|
||||
if (eps.length > 0) {
|
||||
db.transaction((tx) => {
|
||||
for (const ep of seasonData.episodes) {
|
||||
for (const ep of eps) {
|
||||
tx.insert(episodes)
|
||||
.values({
|
||||
seasonId: seasonRow.id,
|
||||
@@ -455,20 +459,23 @@ export async function refreshRecommendations(
|
||||
getSimilar(title.tmdbId, title.type),
|
||||
]);
|
||||
|
||||
const recsResults = recs.results ?? [];
|
||||
const similarResults = similar.results ?? [];
|
||||
|
||||
log.debug(
|
||||
`Fetched ${recs.results.length} recommendations and ${similar.results.length} similar for title ${titleId}`,
|
||||
`Fetched ${recsResults.length} recommendations and ${similarResults.length} similar for title ${titleId}`,
|
||||
);
|
||||
|
||||
// Collect all valid results with their source/rank
|
||||
interface RecItem {
|
||||
result: (typeof recs.results)[0];
|
||||
result: (typeof recsResults)[number];
|
||||
type: "movie" | "tv";
|
||||
source: "tmdb_recommendations" | "tmdb_similar";
|
||||
rank: number;
|
||||
}
|
||||
const allItems: RecItem[] = [];
|
||||
for (let i = 0; i < recs.results.length && i < 20; i++) {
|
||||
const r = recs.results[i];
|
||||
for (let i = 0; i < recsResults.length && i < 20; i++) {
|
||||
const r = recsResults[i];
|
||||
const type = r.media_type ?? title.type;
|
||||
if (type === "movie" || type === "tv") {
|
||||
allItems.push({
|
||||
@@ -479,8 +486,8 @@ export async function refreshRecommendations(
|
||||
});
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < similar.results.length && i < 20; i++) {
|
||||
const r = similar.results[i];
|
||||
for (let i = 0; i < similarResults.length && i < 20; i++) {
|
||||
const r = similarResults[i];
|
||||
const type = r.media_type ?? title.type;
|
||||
if (type === "movie" || type === "tv") {
|
||||
allItems.push({ result: r, type, source: "tmdb_similar", rank: i + 1 });
|
||||
@@ -651,7 +658,7 @@ export async function ensureTvHydrated(
|
||||
})
|
||||
.where(eq(titles.id, titleId))
|
||||
.run();
|
||||
upsertGenres(titleId, show.genres);
|
||||
upsertGenres(titleId, show.genres ?? []);
|
||||
await refreshTvChildren(titleId, tmdbId, show.number_of_seasons);
|
||||
} catch (err) {
|
||||
log.debug(`Failed to hydrate shell TV title ${titleId}:`, err);
|
||||
@@ -807,7 +814,7 @@ export async function getOrFetchTitle(id: string): Promise<{
|
||||
})
|
||||
.where(eq(titles.id, id))
|
||||
.run();
|
||||
upsertGenres(id, movie.genres);
|
||||
upsertGenres(id, movie.genres ?? []);
|
||||
title = db.select().from(titles).where(eq(titles.id, id)).get() ?? title;
|
||||
} catch (err) {
|
||||
log.debug(`Failed to hydrate shell movie title ${id}:`, err);
|
||||
@@ -888,15 +895,15 @@ export function pickBestTrailer(videos: TmdbVideo[]): string | null {
|
||||
const officialTrailers = candidates
|
||||
.filter((v) => v.official && v.type === "Trailer")
|
||||
.sort(byDate);
|
||||
if (officialTrailers.length > 0) return officialTrailers[0].key;
|
||||
if (officialTrailers.length > 0) return officialTrailers[0].key ?? null;
|
||||
|
||||
// Tier 2: any trailer
|
||||
const trailers = candidates.filter((v) => v.type === "Trailer").sort(byDate);
|
||||
if (trailers.length > 0) return trailers[0].key;
|
||||
if (trailers.length > 0) return trailers[0].key ?? null;
|
||||
|
||||
// Tier 3: teasers
|
||||
const teasers = candidates.filter((v) => v.type === "Teaser").sort(byDate);
|
||||
if (teasers.length > 0) return teasers[0].key;
|
||||
if (teasers.length > 0) return teasers[0].key ?? null;
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -907,7 +914,7 @@ export async function refreshTrailer(titleId: string) {
|
||||
|
||||
try {
|
||||
const response = await getVideos(title.tmdbId, title.type);
|
||||
const key = pickBestTrailer(response.results);
|
||||
const key = pickBestTrailer(response.results ?? []);
|
||||
db.update(titles)
|
||||
.set({ trailerVideoKey: key })
|
||||
.where(eq(titles.id, titleId))
|
||||
|
||||
+22
-17
@@ -24,7 +24,7 @@ export async function getOrFetchPerson(
|
||||
const details = await getPersonDetails(person.tmdbId);
|
||||
db.update(persons)
|
||||
.set({
|
||||
name: details.name,
|
||||
name: details.name || person.name,
|
||||
biography: details.biography || null,
|
||||
birthday: details.birthday,
|
||||
deathday: details.deathday,
|
||||
@@ -41,14 +41,14 @@ export async function getOrFetchPerson(
|
||||
return {
|
||||
id: person.id,
|
||||
tmdbId: person.tmdbId,
|
||||
name: details.name,
|
||||
name: details.name || person.name,
|
||||
biography: details.biography || null,
|
||||
birthday: details.birthday,
|
||||
deathday: details.deathday,
|
||||
placeOfBirth: details.place_of_birth,
|
||||
profilePath: tmdbImageUrl(details.profile_path, "profiles"),
|
||||
knownForDepartment: details.known_for_department,
|
||||
imdbId: details.imdb_id,
|
||||
birthday: details.birthday ?? null,
|
||||
deathday: details.deathday ?? null,
|
||||
placeOfBirth: details.place_of_birth ?? null,
|
||||
profilePath: tmdbImageUrl(details.profile_path ?? null, "profiles"),
|
||||
knownForDepartment: details.known_for_department ?? null,
|
||||
imdbId: details.imdb_id ?? null,
|
||||
};
|
||||
} catch (err) {
|
||||
log.error(`Failed to hydrate person ${personId}:`, err);
|
||||
@@ -89,15 +89,15 @@ export async function getOrFetchPersonByTmdbId(
|
||||
.insert(persons)
|
||||
.values({
|
||||
tmdbId,
|
||||
name: details.name,
|
||||
name: details.name ?? "",
|
||||
biography: details.biography || null,
|
||||
birthday: details.birthday,
|
||||
deathday: details.deathday,
|
||||
placeOfBirth: details.place_of_birth,
|
||||
profilePath: details.profile_path,
|
||||
knownForDepartment: details.known_for_department,
|
||||
birthday: details.birthday ?? null,
|
||||
deathday: details.deathday ?? null,
|
||||
placeOfBirth: details.place_of_birth ?? null,
|
||||
profilePath: details.profile_path ?? null,
|
||||
knownForDepartment: details.known_for_department ?? null,
|
||||
popularity: details.popularity,
|
||||
imdbId: details.imdb_id,
|
||||
imdbId: details.imdb_id ?? null,
|
||||
lastFetchedAt: new Date(),
|
||||
})
|
||||
.onConflictDoNothing()
|
||||
@@ -174,7 +174,12 @@ export async function fetchFullFilmography(
|
||||
const credits = await getPersonCombinedCredits(person.tmdbId);
|
||||
|
||||
// Filter to valid cast entries
|
||||
const validCast = credits.cast.filter(
|
||||
// Schema types combined credits cast as movie-only; TV entries also carry
|
||||
// `name` and `first_air_date` at runtime, so widen the type minimally.
|
||||
type CastEntry = (typeof credits)["cast"] extends (infer E)[] | undefined
|
||||
? E & { name?: string; first_air_date?: string }
|
||||
: never;
|
||||
const validCast = ((credits.cast ?? []) as CastEntry[]).filter(
|
||||
(c) => c.media_type === "movie" || c.media_type === "tv",
|
||||
);
|
||||
if (validCast.length === 0) return [];
|
||||
@@ -245,7 +250,7 @@ export async function fetchFullFilmography(
|
||||
tmdbId: c.id,
|
||||
type: c.media_type as "movie" | "tv",
|
||||
title: c.title ?? c.name ?? "Unknown",
|
||||
posterPath: tmdbImageUrl(c.poster_path, "posters"),
|
||||
posterPath: tmdbImageUrl(c.poster_path ?? null, "posters"),
|
||||
releaseDate: c.release_date ?? null,
|
||||
firstAirDate: c.first_air_date ?? null,
|
||||
voteAverage: c.vote_average,
|
||||
|
||||
+19
-32
@@ -162,12 +162,14 @@ async function resolveMovieTmdbId(event: WebhookEvent): Promise<number | null> {
|
||||
|
||||
if (event.imdbId) {
|
||||
const result = await findByExternalId(event.imdbId, "imdb_id");
|
||||
if (result.movie_results.length > 0) return result.movie_results[0].id;
|
||||
const movie = result.movie_results?.[0];
|
||||
if (movie) return movie.id;
|
||||
}
|
||||
|
||||
if (event.tvdbId) {
|
||||
const result = await findByExternalId(event.tvdbId, "tvdb_id");
|
||||
if (result.movie_results.length > 0) return result.movie_results[0].id;
|
||||
const movie = result.movie_results?.[0];
|
||||
if (movie) return movie.id;
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -184,39 +186,27 @@ async function resolveEpisode(event: WebhookEvent): Promise<{
|
||||
// Strategy 1: Use IMDB ID to find the episode and get show_id
|
||||
if (event.imdbId) {
|
||||
const result = await findByExternalId(event.imdbId, "imdb_id");
|
||||
if (result.tv_episode_results.length > 0) {
|
||||
return {
|
||||
showTmdbId: result.tv_episode_results[0].show_id,
|
||||
seasonNumber,
|
||||
episodeNumber,
|
||||
};
|
||||
const ep = result.tv_episode_results?.[0];
|
||||
if (ep) {
|
||||
return { showTmdbId: ep.show_id, seasonNumber, episodeNumber };
|
||||
}
|
||||
// IMDB ID might reference the show itself
|
||||
if (result.tv_results.length > 0) {
|
||||
return {
|
||||
showTmdbId: result.tv_results[0].id,
|
||||
seasonNumber,
|
||||
episodeNumber,
|
||||
};
|
||||
const show = result.tv_results?.[0];
|
||||
if (show) {
|
||||
return { showTmdbId: show.id, seasonNumber, episodeNumber };
|
||||
}
|
||||
}
|
||||
|
||||
// Strategy 2: Use TVDB ID
|
||||
if (event.tvdbId) {
|
||||
const result = await findByExternalId(event.tvdbId, "tvdb_id");
|
||||
if (result.tv_episode_results.length > 0) {
|
||||
return {
|
||||
showTmdbId: result.tv_episode_results[0].show_id,
|
||||
seasonNumber,
|
||||
episodeNumber,
|
||||
};
|
||||
const ep = result.tv_episode_results?.[0];
|
||||
if (ep) {
|
||||
return { showTmdbId: ep.show_id, seasonNumber, episodeNumber };
|
||||
}
|
||||
if (result.tv_results.length > 0) {
|
||||
return {
|
||||
showTmdbId: result.tv_results[0].id,
|
||||
seasonNumber,
|
||||
episodeNumber,
|
||||
};
|
||||
const show = result.tv_results?.[0];
|
||||
if (show) {
|
||||
return { showTmdbId: show.id, seasonNumber, episodeNumber };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,12 +218,9 @@ async function resolveEpisode(event: WebhookEvent): Promise<{
|
||||
// Strategy 4: Search by show title
|
||||
if (event.showTitle) {
|
||||
const searchResult = await searchTv(event.showTitle);
|
||||
if (searchResult.results.length > 0) {
|
||||
return {
|
||||
showTmdbId: searchResult.results[0].id,
|
||||
seasonNumber,
|
||||
episodeNumber,
|
||||
};
|
||||
const tvMatch = searchResult.results?.[0];
|
||||
if (tvMatch) {
|
||||
return { showTmdbId: tvMatch.id, seasonNumber, episodeNumber };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user