movie-web/src/backend/metadata/getmeta.ts

215 lines
5.5 KiB
TypeScript
Raw Normal View History

2023-01-15 15:01:07 +00:00
import { FetchError } from "ofetch";
2023-06-12 19:25:24 +00:00
import { formatJWMeta, mediaTypeToJW } from "./justwatch";
2023-06-13 09:01:07 +00:00
import {
2023-06-29 19:21:24 +00:00
TMDBIdToUrlId,
TMDBMediaToMediaType,
formatTMDBMeta,
2023-06-21 11:07:33 +00:00
getEpisodes,
getMediaDetails,
2023-06-21 11:26:03 +00:00
getMediaPoster,
2023-06-21 16:16:41 +00:00
getMovieFromExternalId,
mediaTypeToTMDB,
} from "./tmdb";
2023-01-15 15:01:07 +00:00
import {
2023-06-29 19:10:17 +00:00
JWDetailedMeta,
JWSeasonMetaResult,
2023-01-15 15:01:07 +00:00
JW_API_BASE,
2023-06-21 11:23:39 +00:00
} from "./types/justwatch";
import { MWMediaMeta, MWMediaType } from "./types/mw";
import {
2023-06-30 10:20:01 +00:00
TMDBContentTypes,
TMDBMediaResult,
2023-06-13 08:41:54 +00:00
TMDBMovieData,
TMDBSeasonMetaResult,
2023-06-13 08:41:54 +00:00
TMDBShowData,
2023-06-21 11:23:39 +00:00
} from "./types/tmdb";
import { makeUrl, proxiedFetch } from "../helpers/fetch";
export interface DetailedMeta {
meta: MWMediaMeta;
2023-05-21 16:12:45 +00:00
imdbId?: string;
2023-05-21 19:00:35 +00:00
tmdbId?: string;
}
2023-06-15 20:13:19 +00:00
export function formatTMDBMetaResult(
2023-06-14 05:48:31 +00:00
details: TMDBShowData | TMDBMovieData,
type: MWMediaType,
2023-06-21 10:43:36 +00:00
): TMDBMediaResult {
2023-06-14 05:48:31 +00:00
if (type === MWMediaType.MOVIE) {
2023-06-21 10:47:09 +00:00
const movie = details as TMDBMovieData;
2023-06-21 10:48:33 +00:00
return {
2023-06-14 05:48:31 +00:00
id: details.id,
2023-06-21 10:47:09 +00:00
title: movie.title,
2023-06-14 05:48:31 +00:00
object_type: mediaTypeToTMDB(type),
2023-06-21 11:26:03 +00:00
poster: getMediaPoster(movie.poster_path) ?? undefined,
original_release_year: new Date(movie.release_date).getFullYear(),
2023-06-14 05:48:31 +00:00
};
}
if (type === MWMediaType.SERIES) {
2023-06-21 10:47:09 +00:00
const show = details as TMDBShowData;
2023-06-21 10:48:33 +00:00
return {
2023-06-14 05:48:31 +00:00
id: details.id,
2023-06-21 10:47:09 +00:00
title: show.name,
2023-06-14 05:48:31 +00:00
object_type: mediaTypeToTMDB(type),
2023-06-21 10:47:09 +00:00
seasons: show.seasons.map((v) => ({
2023-06-14 05:48:31 +00:00
id: v.id,
season_number: v.season_number,
title: v.name,
})),
2023-06-23 12:20:04 +00:00
poster: getMediaPoster(show.poster_path) ?? undefined,
original_release_year: new Date(show.first_air_date).getFullYear(),
2023-06-14 05:48:31 +00:00
};
}
2023-06-21 10:43:36 +00:00
2023-06-21 10:48:33 +00:00
throw new Error("unsupported type");
2023-06-14 05:48:31 +00:00
}
export async function getMetaFromId(
type: MWMediaType,
id: string,
seasonId?: string,
2023-06-13 08:41:54 +00:00
): Promise<DetailedMeta | null> {
2023-06-21 11:07:33 +00:00
const details = await getMediaDetails(id, mediaTypeToTMDB(type));
2023-06-13 08:41:54 +00:00
if (!details) return null;
2023-06-30 10:57:29 +00:00
const imdbId = details.external_ids.imdb_id ?? undefined;
2023-06-13 08:41:54 +00:00
let seasonData: TMDBSeasonMetaResult | undefined;
2023-06-13 08:41:54 +00:00
if (type === MWMediaType.SERIES) {
const seasons = (details as TMDBShowData).seasons;
let selectedSeason = seasons.find((v) => v.id.toString() === seasonId);
if (!selectedSeason) {
selectedSeason = seasons.find((v) => v.season_number === 1);
}
if (selectedSeason) {
const episodes = await getEpisodes(
details.id.toString(),
selectedSeason.season_number,
);
2023-06-13 08:41:54 +00:00
seasonData = {
id: selectedSeason.id.toString(),
season_number: selectedSeason.season_number,
title: selectedSeason.name,
2023-06-13 08:41:54 +00:00
episodes,
};
}
}
2023-06-15 20:13:19 +00:00
const tmdbmeta = formatTMDBMetaResult(details, type);
2023-06-14 05:48:31 +00:00
if (!tmdbmeta) return null;
const meta = formatTMDBMeta(tmdbmeta, seasonData);
if (!meta) return null;
2023-06-13 08:41:54 +00:00
return {
meta,
imdbId,
tmdbId: id,
};
}
export async function getLegacyMetaFromId(
type: MWMediaType,
id: string,
seasonId?: string,
2023-01-15 15:01:07 +00:00
): Promise<DetailedMeta | null> {
const queryType = mediaTypeToJW(type);
let data: JWDetailedMeta;
try {
const url = makeUrl("/content/titles/{type}/{id}/locale/en_US", {
type: queryType,
id,
});
data = await proxiedFetch<JWDetailedMeta>(url, { baseURL: JW_API_BASE });
2023-01-15 15:01:07 +00:00
} catch (err) {
if (err instanceof FetchError) {
// 400 and 404 are treated as not found
if (err.statusCode === 400 || err.statusCode === 404) return null;
}
throw err;
}
let imdbId = data.external_ids.find((v) => v.provider === "imdb_latest")
?.external_id;
2023-03-10 19:54:56 +00:00
if (!imdbId)
2023-03-10 19:59:10 +00:00
imdbId = data.external_ids.find((v) => v.provider === "imdb")?.external_id;
2023-03-10 19:54:56 +00:00
let tmdbId = data.external_ids.find((v) => v.provider === "tmdb_latest")
?.external_id;
2023-05-21 19:00:35 +00:00
if (!tmdbId)
tmdbId = data.external_ids.find((v) => v.provider === "tmdb")?.external_id;
let seasonData: JWSeasonMetaResult | undefined;
if (data.object_type === "show") {
const seasonToScrape = seasonId ?? data.seasons?.[0].id.toString() ?? "";
const url = makeUrl("/content/titles/show_season/{id}/locale/en_US", {
id: seasonToScrape,
});
seasonData = await proxiedFetch<any>(url, { baseURL: JW_API_BASE });
}
return {
meta: formatJWMeta(data, seasonData),
imdbId,
2023-05-21 19:00:35 +00:00
tmdbId,
};
}
2023-06-13 09:01:07 +00:00
2023-06-21 19:35:25 +00:00
export function isLegacyUrl(url: string): boolean {
2023-06-30 10:57:29 +00:00
if (url.startsWith("/media/JW") || url.startsWith("/media/tmdb-show"))
return true;
return false;
}
export function isLegacyMediaType(url: string): boolean {
if (url.startsWith("/media/tmdb-show")) return true;
2023-06-21 19:35:25 +00:00
return false;
}
2023-06-13 12:06:37 +00:00
export async function convertLegacyUrl(
url: string,
2023-06-13 12:06:37 +00:00
): Promise<string | undefined> {
2023-06-21 19:35:25 +00:00
if (!isLegacyUrl(url)) return undefined;
2023-06-21 16:16:41 +00:00
2023-06-21 19:35:25 +00:00
const urlParts = url.split("/").slice(2);
const [, type, id] = urlParts[0].split("-", 3);
2023-10-21 20:24:10 +00:00
const suffix = urlParts
.slice(1)
.map((v) => `/${v}`)
.join("");
2023-06-21 16:16:41 +00:00
2023-06-30 10:57:29 +00:00
if (isLegacyMediaType(url)) {
const details = await getMediaDetails(id, TMDBContentTypes.TV);
return `/media/${TMDBIdToUrlId(
MWMediaType.SERIES,
details.id.toString(),
details.name,
2023-10-21 20:24:10 +00:00
)}${suffix}`;
2023-06-30 10:57:29 +00:00
}
2023-06-30 10:20:01 +00:00
const mediaType = TMDBMediaToMediaType(type as TMDBContentTypes);
2023-06-21 19:35:25 +00:00
const meta = await getLegacyMetaFromId(mediaType, id);
2023-06-21 16:16:41 +00:00
2023-06-21 19:35:25 +00:00
if (!meta) return undefined;
const { tmdbId, imdbId } = meta;
if (!tmdbId && !imdbId) return undefined;
2023-06-21 16:16:41 +00:00
2023-06-21 19:35:25 +00:00
// movies always have an imdb id on tmdb
if (imdbId && mediaType === MWMediaType.MOVIE) {
const movieId = await getMovieFromExternalId(imdbId);
2023-06-29 19:21:24 +00:00
if (movieId) {
return `/media/${TMDBIdToUrlId(mediaType, movieId, meta.meta.title)}`;
}
2023-06-21 19:35:25 +00:00
2023-06-29 19:21:24 +00:00
if (tmdbId) {
return `/media/${TMDBIdToUrlId(mediaType, tmdbId, meta.meta.title)}`;
}
2023-06-13 12:06:37 +00:00
}
}