mirror of
https://github.com/movie-web/movie-web.git
synced 2024-12-27 23:01:50 +01:00
refactor everything to use tmdb exclusively
This commit is contained in:
parent
8da155ba2b
commit
46bd20f718
@ -1,22 +1,22 @@
|
|||||||
import { FetchError } from "ofetch";
|
import { FetchError } from "ofetch";
|
||||||
|
|
||||||
import { formatJWMeta, mediaTypeToJW } from "./justwatch";
|
import { formatJWMeta, mediaTypeToJW } from "./justwatch";
|
||||||
import { Tmdb } from "./tmdb";
|
|
||||||
import {
|
import {
|
||||||
TTVMediaToMediaType,
|
TMDBMediaToMediaType,
|
||||||
Trakt,
|
Tmdb,
|
||||||
formatTTVMeta,
|
formatTMDBMeta,
|
||||||
mediaTypeToTTV,
|
mediaTypeToTMDB,
|
||||||
} from "./trakttv";
|
} from "./tmdb";
|
||||||
import {
|
import {
|
||||||
JWMediaResult,
|
JWMediaResult,
|
||||||
JWSeasonMetaResult,
|
JWSeasonMetaResult,
|
||||||
JW_API_BASE,
|
JW_API_BASE,
|
||||||
MWMediaMeta,
|
MWMediaMeta,
|
||||||
MWMediaType,
|
MWMediaType,
|
||||||
|
TMDBMediaResult,
|
||||||
TMDBMovieData,
|
TMDBMovieData,
|
||||||
|
TMDBSeasonMetaResult,
|
||||||
TMDBShowData,
|
TMDBShowData,
|
||||||
TTVSeasonMetaResult,
|
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import { makeUrl, proxiedFetch } from "../helpers/fetch";
|
import { makeUrl, proxiedFetch } from "../helpers/fetch";
|
||||||
|
|
||||||
@ -48,9 +48,7 @@ export async function getMetaFromId(
|
|||||||
id: string,
|
id: string,
|
||||||
seasonId?: string
|
seasonId?: string
|
||||||
): Promise<DetailedMeta | null> {
|
): Promise<DetailedMeta | null> {
|
||||||
const result = await Trakt.searchById(id, mediaTypeToJW(type));
|
const details = await Tmdb.getMediaDetails(id, mediaTypeToTMDB(type));
|
||||||
if (!result) return null;
|
|
||||||
const details = await Tmdb.getMediaDetails(id, type);
|
|
||||||
|
|
||||||
if (!details) return null;
|
if (!details) return null;
|
||||||
|
|
||||||
@ -59,15 +57,15 @@ export async function getMetaFromId(
|
|||||||
imdbId = (details as TMDBMovieData).imdb_id ?? undefined;
|
imdbId = (details as TMDBMovieData).imdb_id ?? undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
let seasonData: TTVSeasonMetaResult | undefined;
|
let seasonData: TMDBSeasonMetaResult | undefined;
|
||||||
|
|
||||||
if (type === MWMediaType.SERIES) {
|
if (type === MWMediaType.SERIES) {
|
||||||
const seasons = (details as TMDBShowData).seasons;
|
const seasons = (details as TMDBShowData).seasons;
|
||||||
const season =
|
const season =
|
||||||
seasons?.find((v) => v.id.toString() === seasonId) ?? seasons?.[0];
|
seasons?.find((v) => v.id.toString() === seasonId) ?? seasons?.[0];
|
||||||
|
|
||||||
const episodes = await Trakt.getEpisodes(
|
const episodes = await Tmdb.getEpisodes(
|
||||||
result.ttv_entity_id,
|
details.id.toString(),
|
||||||
season?.season_number ?? 1
|
season?.season_number ?? 1
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -81,10 +79,27 @@ export async function getMetaFromId(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const meta = formatTTVMeta(result, seasonData);
|
const tmdbmeta: TMDBMediaResult = {
|
||||||
if (!meta) return null;
|
id: details.id,
|
||||||
|
title:
|
||||||
|
type === MWMediaType.MOVIE
|
||||||
|
? (details as TMDBMovieData).title
|
||||||
|
: (details as TMDBShowData).name,
|
||||||
|
object_type: mediaTypeToTMDB(type),
|
||||||
|
seasons: (details as TMDBShowData).seasons.map((v) => ({
|
||||||
|
id: v.id,
|
||||||
|
season_number: v.season_number,
|
||||||
|
title: v.name,
|
||||||
|
})),
|
||||||
|
poster: (details as TMDBMovieData).poster_path ?? undefined,
|
||||||
|
original_release_year:
|
||||||
|
type === MWMediaType.MOVIE
|
||||||
|
? Number((details as TMDBMovieData).release_date?.split("-")[0])
|
||||||
|
: Number((details as TMDBShowData).first_air_date?.split("-")[0]),
|
||||||
|
};
|
||||||
|
|
||||||
console.log(meta);
|
const meta = formatTMDBMeta(tmdbmeta, seasonData);
|
||||||
|
if (!meta) return null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
meta,
|
meta,
|
||||||
@ -143,18 +158,18 @@ export async function getLegacyMetaFromId(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TTVMediaToId(media: MWMediaMeta): string {
|
export function TMDBMediaToId(media: MWMediaMeta): string {
|
||||||
return ["TTV", mediaTypeToTTV(media.type), media.id].join("-");
|
return ["tmdb", mediaTypeToTMDB(media.type), media.id].join("-");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function decodeTTVId(
|
export function decodeTMDBId(
|
||||||
paramId: string
|
paramId: string
|
||||||
): { id: string; type: MWMediaType } | null {
|
): { id: string; type: MWMediaType } | null {
|
||||||
const [prefix, type, id] = paramId.split("-", 3);
|
const [prefix, type, id] = paramId.split("-", 3);
|
||||||
if (prefix !== "TTV") return null;
|
if (prefix !== "tmdb") return null;
|
||||||
let mediaType;
|
let mediaType;
|
||||||
try {
|
try {
|
||||||
mediaType = TTVMediaToMediaType(type);
|
mediaType = TMDBMediaToMediaType(type);
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -170,11 +185,11 @@ export async function convertLegacyUrl(
|
|||||||
if (url.startsWith("/media/JW")) {
|
if (url.startsWith("/media/JW")) {
|
||||||
const urlParts = url.split("/").slice(2);
|
const urlParts = url.split("/").slice(2);
|
||||||
const [, type, id] = urlParts[0].split("-", 3);
|
const [, type, id] = urlParts[0].split("-", 3);
|
||||||
const meta = await getLegacyMetaFromId(TTVMediaToMediaType(type), id);
|
const meta = await getLegacyMetaFromId(TMDBMediaToMediaType(type), id);
|
||||||
if (!meta) return undefined;
|
if (!meta) return undefined;
|
||||||
const tmdbId = meta.tmdbId;
|
const tmdbId = meta.tmdbId;
|
||||||
if (!tmdbId) return undefined;
|
if (!tmdbId) return undefined;
|
||||||
return `/media/TTV-${type}-${tmdbId}`;
|
return `/media/tmdb-${type}-${tmdbId}`;
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
import { SimpleCache } from "@/utils/cache";
|
import { SimpleCache } from "@/utils/cache";
|
||||||
|
|
||||||
import { Trakt, mediaTypeToTTV } from "./trakttv";
|
import {
|
||||||
|
Tmdb,
|
||||||
|
formatTMDBMeta,
|
||||||
|
formatTMDBSearchResult,
|
||||||
|
mediaTypeToTMDB,
|
||||||
|
} from "./tmdb";
|
||||||
import { MWMediaMeta, MWQuery } from "./types";
|
import { MWMediaMeta, MWQuery } from "./types";
|
||||||
|
|
||||||
const cache = new SimpleCache<MWQuery, MWMediaMeta[]>();
|
const cache = new SimpleCache<MWQuery, MWMediaMeta[]>();
|
||||||
@ -13,10 +18,17 @@ export async function searchForMedia(query: MWQuery): Promise<MWMediaMeta[]> {
|
|||||||
if (cache.has(query)) return cache.get(query) as MWMediaMeta[];
|
if (cache.has(query)) return cache.get(query) as MWMediaMeta[];
|
||||||
const { searchQuery, type } = query;
|
const { searchQuery, type } = query;
|
||||||
|
|
||||||
const contentType = mediaTypeToTTV(type);
|
const data = await Tmdb.searchMedia(searchQuery, mediaTypeToTMDB(type));
|
||||||
|
const results = await Promise.all(
|
||||||
|
data.results.map(async (v) => {
|
||||||
|
const formattedResult = await formatTMDBSearchResult(
|
||||||
|
v,
|
||||||
|
mediaTypeToTMDB(type)
|
||||||
|
);
|
||||||
|
return formatTMDBMeta(formattedResult);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
const results = await Trakt.search(searchQuery, contentType);
|
|
||||||
console.log(results[0]);
|
|
||||||
cache.set(query, results, 3600);
|
cache.set(query, results, 3600);
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,100 @@
|
|||||||
import { conf } from "@/setup/config";
|
import { conf } from "@/setup/config";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
MWMediaMeta,
|
||||||
MWMediaType,
|
MWMediaType,
|
||||||
|
MWSeasonMeta,
|
||||||
|
TMDBContentTypes,
|
||||||
|
TMDBEpisodeShort,
|
||||||
|
TMDBMediaResult,
|
||||||
TMDBMediaStatic,
|
TMDBMediaStatic,
|
||||||
TMDBMovieData,
|
TMDBMovieData,
|
||||||
|
TMDBMovieResponse,
|
||||||
|
TMDBMovieResult,
|
||||||
|
TMDBSearchResultStatic,
|
||||||
|
TMDBSeason,
|
||||||
|
TMDBSeasonMetaResult,
|
||||||
TMDBShowData,
|
TMDBShowData,
|
||||||
|
TMDBShowResponse,
|
||||||
|
TMDBShowResult,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import { mwFetch } from "../helpers/fetch";
|
import { mwFetch } from "../helpers/fetch";
|
||||||
|
|
||||||
|
export function mediaTypeToTMDB(type: MWMediaType): TMDBContentTypes {
|
||||||
|
if (type === MWMediaType.MOVIE) return "movie";
|
||||||
|
if (type === MWMediaType.SERIES) return "show";
|
||||||
|
throw new Error("unsupported type");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TMDBMediaToMediaType(type: string): MWMediaType {
|
||||||
|
if (type === "movie") return MWMediaType.MOVIE;
|
||||||
|
if (type === "show") return MWMediaType.SERIES;
|
||||||
|
throw new Error("unsupported type");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatTMDBMeta(
|
||||||
|
media: TMDBMediaResult,
|
||||||
|
season?: TMDBSeasonMetaResult
|
||||||
|
): MWMediaMeta {
|
||||||
|
const type = TMDBMediaToMediaType(media.object_type);
|
||||||
|
let seasons: undefined | MWSeasonMeta[];
|
||||||
|
if (type === MWMediaType.SERIES) {
|
||||||
|
seasons = media.seasons
|
||||||
|
?.sort((a, b) => a.season_number - b.season_number)
|
||||||
|
.map(
|
||||||
|
(v): MWSeasonMeta => ({
|
||||||
|
title: v.title,
|
||||||
|
id: v.id.toString(),
|
||||||
|
number: v.season_number,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: media.title,
|
||||||
|
id: media.id.toString(),
|
||||||
|
year: media.original_release_year?.toString(),
|
||||||
|
poster: media.poster,
|
||||||
|
type,
|
||||||
|
seasons: seasons as any,
|
||||||
|
seasonData: season
|
||||||
|
? ({
|
||||||
|
id: season.id.toString(),
|
||||||
|
number: season.season_number,
|
||||||
|
title: season.title,
|
||||||
|
episodes: season.episodes
|
||||||
|
.sort((a, b) => a.episode_number - b.episode_number)
|
||||||
|
.map((v) => ({
|
||||||
|
id: v.id.toString(),
|
||||||
|
number: v.episode_number,
|
||||||
|
title: v.title,
|
||||||
|
})),
|
||||||
|
} as any)
|
||||||
|
: (undefined as any),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TMDBMediaToId(media: MWMediaMeta): string {
|
||||||
|
return ["tmdb", mediaTypeToTMDB(media.type), media.id].join("-");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decodeTMDBId(
|
||||||
|
paramId: string
|
||||||
|
): { id: string; type: MWMediaType } | null {
|
||||||
|
const [prefix, type, id] = paramId.split("-", 3);
|
||||||
|
if (prefix !== "tmdb") return null;
|
||||||
|
let mediaType;
|
||||||
|
try {
|
||||||
|
mediaType = TMDBMediaToMediaType(type);
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
type: mediaType,
|
||||||
|
id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export abstract class Tmdb {
|
export abstract class Tmdb {
|
||||||
private static baseURL = "https://api.themoviedb.org/3";
|
private static baseURL = "https://api.themoviedb.org/3";
|
||||||
|
|
||||||
@ -24,9 +111,33 @@ export abstract class Tmdb {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static searchMedia: TMDBSearchResultStatic["searchMedia"] = async (
|
||||||
|
query: string,
|
||||||
|
type: TMDBContentTypes
|
||||||
|
) => {
|
||||||
|
let data;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case "movie":
|
||||||
|
data = await Tmdb.get<TMDBMovieResponse>(
|
||||||
|
`search/movie?query=${query}&include_adult=true&language=en-US&page=1`
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "show":
|
||||||
|
data = await Tmdb.get<TMDBShowResponse>(
|
||||||
|
`search/tv?query=${query}&include_adult=true&language=en-US&page=1`
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error("Invalid media type");
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
public static getMediaDetails: TMDBMediaStatic["getMediaDetails"] = async (
|
public static getMediaDetails: TMDBMediaStatic["getMediaDetails"] = async (
|
||||||
id: string,
|
id: string,
|
||||||
type: MWMediaType
|
type: TMDBContentTypes
|
||||||
) => {
|
) => {
|
||||||
let data;
|
let data;
|
||||||
|
|
||||||
@ -34,7 +145,7 @@ export abstract class Tmdb {
|
|||||||
case "movie":
|
case "movie":
|
||||||
data = await Tmdb.get<TMDBMovieData>(`/movie/${id}`);
|
data = await Tmdb.get<TMDBMovieData>(`/movie/${id}`);
|
||||||
break;
|
break;
|
||||||
case "series":
|
case "show":
|
||||||
data = await Tmdb.get<TMDBShowData>(`/tv/${id}`);
|
data = await Tmdb.get<TMDBShowData>(`/tv/${id}`);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -47,4 +158,48 @@ export abstract class Tmdb {
|
|||||||
public static getMediaPoster(posterPath: string | null): string | undefined {
|
public static getMediaPoster(posterPath: string | null): string | undefined {
|
||||||
if (posterPath) return `https://image.tmdb.org/t/p/w185/${posterPath}`;
|
if (posterPath) return `https://image.tmdb.org/t/p/w185/${posterPath}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async getEpisodes(
|
||||||
|
id: string,
|
||||||
|
season: number
|
||||||
|
): Promise<TMDBEpisodeShort[]> {
|
||||||
|
const data = await Tmdb.get<TMDBSeason>(`/tv/${id}/season/${season}`);
|
||||||
|
return data.episodes.map((e) => ({
|
||||||
|
id: e.id,
|
||||||
|
episode_number: e.episode_number,
|
||||||
|
title: e.name,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function formatTMDBSearchResult(
|
||||||
|
result: TMDBShowResult | TMDBMovieResult,
|
||||||
|
mediatype: TMDBContentTypes
|
||||||
|
): Promise<TMDBMediaResult> {
|
||||||
|
const type = TMDBMediaToMediaType(mediatype);
|
||||||
|
const details = await Tmdb.getMediaDetails(result.id.toString(), mediatype);
|
||||||
|
|
||||||
|
const seasons =
|
||||||
|
type === MWMediaType.SERIES
|
||||||
|
? (details as TMDBShowData).seasons?.map((v) => ({
|
||||||
|
id: v.id,
|
||||||
|
title: v.name,
|
||||||
|
season_number: v.season_number,
|
||||||
|
}))
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
title:
|
||||||
|
type === MWMediaType.SERIES
|
||||||
|
? (result as TMDBShowResult).name
|
||||||
|
: (result as TMDBMovieResult).title,
|
||||||
|
poster: Tmdb.getMediaPoster(details.poster_path),
|
||||||
|
id: result.id,
|
||||||
|
original_release_year:
|
||||||
|
type === MWMediaType.SERIES
|
||||||
|
? Number((result as TMDBShowResult).first_air_date?.split("-")[0])
|
||||||
|
: Number((result as TMDBMovieResult).release_date?.split("-")[0]),
|
||||||
|
object_type: mediaTypeToTMDB(type),
|
||||||
|
seasons,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,187 +0,0 @@
|
|||||||
import { conf } from "@/setup/config";
|
|
||||||
|
|
||||||
import { Tmdb } from "./tmdb";
|
|
||||||
import {
|
|
||||||
MWMediaMeta,
|
|
||||||
MWMediaType,
|
|
||||||
MWSeasonMeta,
|
|
||||||
TMDBShowData,
|
|
||||||
TTVContentTypes,
|
|
||||||
TTVEpisodeResult,
|
|
||||||
TTVEpisodeShort,
|
|
||||||
TTVMediaResult,
|
|
||||||
TTVSearchResult,
|
|
||||||
TTVSeasonMetaResult,
|
|
||||||
} from "./types";
|
|
||||||
import { mwFetch } from "../helpers/fetch";
|
|
||||||
|
|
||||||
export function mediaTypeToTTV(type: MWMediaType): TTVContentTypes {
|
|
||||||
if (type === MWMediaType.MOVIE) return "movie";
|
|
||||||
if (type === MWMediaType.SERIES) return "show";
|
|
||||||
throw new Error("unsupported type");
|
|
||||||
}
|
|
||||||
|
|
||||||
export function TTVMediaToMediaType(type: string): MWMediaType {
|
|
||||||
if (type === "movie") return MWMediaType.MOVIE;
|
|
||||||
if (type === "show") return MWMediaType.SERIES;
|
|
||||||
throw new Error("unsupported type");
|
|
||||||
}
|
|
||||||
|
|
||||||
export function formatTTVMeta(
|
|
||||||
media: TTVMediaResult,
|
|
||||||
season?: TTVSeasonMetaResult
|
|
||||||
): MWMediaMeta {
|
|
||||||
const type = TTVMediaToMediaType(media.object_type);
|
|
||||||
let seasons: undefined | MWSeasonMeta[];
|
|
||||||
if (type === MWMediaType.SERIES) {
|
|
||||||
seasons = media.seasons
|
|
||||||
?.sort((a, b) => a.season_number - b.season_number)
|
|
||||||
.map(
|
|
||||||
(v): MWSeasonMeta => ({
|
|
||||||
title: v.title,
|
|
||||||
id: v.id.toString(),
|
|
||||||
number: v.season_number,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
title: media.title,
|
|
||||||
id: media.id.toString(),
|
|
||||||
year: media.original_release_year?.toString(),
|
|
||||||
poster: media.poster,
|
|
||||||
type,
|
|
||||||
seasons: seasons as any,
|
|
||||||
seasonData: season
|
|
||||||
? ({
|
|
||||||
id: season.id.toString(),
|
|
||||||
number: season.season_number,
|
|
||||||
title: season.title,
|
|
||||||
episodes: season.episodes
|
|
||||||
.sort((a, b) => a.episode_number - b.episode_number)
|
|
||||||
.map((v) => ({
|
|
||||||
id: v.id.toString(),
|
|
||||||
number: v.episode_number,
|
|
||||||
title: v.title,
|
|
||||||
})),
|
|
||||||
} as any)
|
|
||||||
: (undefined as any),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function TTVMediaToId(media: MWMediaMeta): string {
|
|
||||||
return ["MW", mediaTypeToTTV(media.type), media.id].join("-");
|
|
||||||
}
|
|
||||||
|
|
||||||
export function decodeTTVId(
|
|
||||||
paramId: string
|
|
||||||
): { id: string; type: MWMediaType } | null {
|
|
||||||
const [prefix, type, id] = paramId.split("-", 3);
|
|
||||||
if (prefix !== "MW") return null;
|
|
||||||
let mediaType;
|
|
||||||
try {
|
|
||||||
mediaType = TTVMediaToMediaType(type);
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
type: mediaType,
|
|
||||||
id,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function formatTTVSearchResult(
|
|
||||||
result: TTVSearchResult
|
|
||||||
): Promise<TTVMediaResult> {
|
|
||||||
const type = TTVMediaToMediaType(result.type);
|
|
||||||
const media = result[result.type];
|
|
||||||
|
|
||||||
if (!media) throw new Error("invalid result");
|
|
||||||
|
|
||||||
const details = await Tmdb.getMediaDetails(
|
|
||||||
media.ids.tmdb.toString(),
|
|
||||||
TTVMediaToMediaType(result.type)
|
|
||||||
);
|
|
||||||
|
|
||||||
const seasons =
|
|
||||||
type === MWMediaType.SERIES
|
|
||||||
? (details as TMDBShowData).seasons?.map((v) => ({
|
|
||||||
id: v.id,
|
|
||||||
title: v.name,
|
|
||||||
season_number: v.season_number,
|
|
||||||
}))
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
return {
|
|
||||||
title: media.title,
|
|
||||||
poster: Tmdb.getMediaPoster(details.poster_path),
|
|
||||||
id: media.ids.tmdb,
|
|
||||||
original_release_year: media.year,
|
|
||||||
ttv_entity_id: media.ids.slug,
|
|
||||||
object_type: mediaTypeToTTV(type),
|
|
||||||
seasons,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export abstract class Trakt {
|
|
||||||
private static baseURL = "https://api.trakt.tv";
|
|
||||||
|
|
||||||
private static headers = {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"trakt-api-version": "2",
|
|
||||||
"trakt-api-key": conf().TRAKT_CLIENT_ID,
|
|
||||||
};
|
|
||||||
|
|
||||||
private static async get<T>(url: string): Promise<T> {
|
|
||||||
const res = await mwFetch<any>(url, {
|
|
||||||
headers: Trakt.headers,
|
|
||||||
baseURL: Trakt.baseURL,
|
|
||||||
});
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async search(
|
|
||||||
query: string,
|
|
||||||
type: "movie" | "show"
|
|
||||||
): Promise<MWMediaMeta[]> {
|
|
||||||
const data = await Trakt.get<TTVSearchResult[]>(
|
|
||||||
`/search/${type}?query=${encodeURIComponent(query)}`
|
|
||||||
);
|
|
||||||
|
|
||||||
const formatted = await Promise.all(
|
|
||||||
// eslint-disable-next-line no-return-await
|
|
||||||
data.map(async (v) => await formatTTVSearchResult(v))
|
|
||||||
);
|
|
||||||
return formatted.map((v) => formatTTVMeta(v));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async searchById(
|
|
||||||
tmdbId: string,
|
|
||||||
type: "movie" | "show"
|
|
||||||
): Promise<TTVMediaResult> {
|
|
||||||
const data = await Trakt.get<TTVSearchResult[]>(
|
|
||||||
`/search/tmdb/${tmdbId}?type=${type}`
|
|
||||||
);
|
|
||||||
|
|
||||||
const formatted = await Promise.all(
|
|
||||||
// eslint-disable-next-line no-return-await
|
|
||||||
data.map(async (v) => await formatTTVSearchResult(v))
|
|
||||||
);
|
|
||||||
return formatted[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async getEpisodes(
|
|
||||||
slug: string,
|
|
||||||
season: number
|
|
||||||
): Promise<TTVEpisodeShort[]> {
|
|
||||||
const data = await Trakt.get<TTVEpisodeResult[]>(
|
|
||||||
`/shows/${slug}/seasons/${season}`
|
|
||||||
);
|
|
||||||
|
|
||||||
return data.map((e) => ({
|
|
||||||
id: e.ids.tmdb,
|
|
||||||
episode_number: e.number,
|
|
||||||
title: e.title,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
@ -46,63 +46,36 @@ export interface MWQuery {
|
|||||||
type: MWMediaType;
|
type: MWMediaType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TTVContentTypes = "movie" | "show";
|
export type TMDBContentTypes = "movie" | "show";
|
||||||
|
|
||||||
export type TTVSeasonShort = {
|
export type TMDBSeasonShort = {
|
||||||
title: string;
|
title: string;
|
||||||
id: number;
|
id: number;
|
||||||
season_number: number;
|
season_number: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TTVEpisodeShort = {
|
export type TMDBEpisodeShort = {
|
||||||
title: string;
|
title: string;
|
||||||
id: number;
|
id: number;
|
||||||
episode_number: number;
|
episode_number: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TTVMediaResult = {
|
export type TMDBMediaResult = {
|
||||||
title: string;
|
title: string;
|
||||||
poster?: string;
|
poster?: string;
|
||||||
id: number;
|
id: number;
|
||||||
original_release_year?: number;
|
original_release_year?: number;
|
||||||
ttv_entity_id: string;
|
object_type: TMDBContentTypes;
|
||||||
object_type: TTVContentTypes;
|
seasons?: TMDBSeasonShort[];
|
||||||
seasons?: TTVSeasonShort[];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TTVSeasonMetaResult = {
|
export type TMDBSeasonMetaResult = {
|
||||||
title: string;
|
title: string;
|
||||||
id: string;
|
id: string;
|
||||||
season_number: number;
|
season_number: number;
|
||||||
episodes: TTVEpisodeShort[];
|
episodes: TMDBEpisodeShort[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface TTVSearchResult {
|
|
||||||
type: "movie" | "show";
|
|
||||||
score: number;
|
|
||||||
movie?: {
|
|
||||||
title: string;
|
|
||||||
year: number;
|
|
||||||
ids: {
|
|
||||||
trakt: number;
|
|
||||||
slug: string;
|
|
||||||
imdb: string;
|
|
||||||
tmdb: number;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
show?: {
|
|
||||||
title: string;
|
|
||||||
year: number;
|
|
||||||
ids: {
|
|
||||||
trakt: number;
|
|
||||||
slug: string;
|
|
||||||
tvdb: number;
|
|
||||||
imdb: string;
|
|
||||||
tmdb: number;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DetailedMeta {
|
export interface DetailedMeta {
|
||||||
meta: MWMediaMeta;
|
meta: MWMediaMeta;
|
||||||
imdbId?: string;
|
imdbId?: string;
|
||||||
@ -255,12 +228,9 @@ export interface TMDBMovieData {
|
|||||||
export type TMDBMediaDetailsPromise = Promise<TMDBShowData | TMDBMovieData>;
|
export type TMDBMediaDetailsPromise = Promise<TMDBShowData | TMDBMovieData>;
|
||||||
|
|
||||||
export interface TMDBMediaStatic {
|
export interface TMDBMediaStatic {
|
||||||
getMediaDetails(
|
getMediaDetails(id: string, type: "show"): TMDBMediaDetailsPromise;
|
||||||
id: string,
|
getMediaDetails(id: string, type: "movie"): TMDBMediaDetailsPromise;
|
||||||
type: MWMediaType.SERIES
|
getMediaDetails(id: string, type: TMDBContentTypes): TMDBMediaDetailsPromise;
|
||||||
): TMDBMediaDetailsPromise;
|
|
||||||
getMediaDetails(id: string, type: MWMediaType.MOVIE): TMDBMediaDetailsPromise;
|
|
||||||
getMediaDetails(id: string, type: MWMediaType): TMDBMediaDetailsPromise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type JWContentTypes = "movie" | "show";
|
export type JWContentTypes = "movie" | "show";
|
||||||
@ -312,7 +282,7 @@ export type JWSeasonMetaResult = {
|
|||||||
episodes: JWEpisodeShort[];
|
episodes: JWEpisodeShort[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface TTVEpisodeResult {
|
export interface TMDBEpisodeResult {
|
||||||
season: number;
|
season: number;
|
||||||
number: number;
|
number: number;
|
||||||
title: string;
|
title: string;
|
||||||
@ -323,3 +293,89 @@ export interface TTVEpisodeResult {
|
|||||||
tmdb: number;
|
tmdb: number;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TMDBShowResult {
|
||||||
|
adult: boolean;
|
||||||
|
backdrop_path: string | null;
|
||||||
|
genre_ids: number[];
|
||||||
|
id: number;
|
||||||
|
origin_country: string[];
|
||||||
|
original_language: string;
|
||||||
|
original_name: string;
|
||||||
|
overview: string;
|
||||||
|
popularity: number;
|
||||||
|
poster_path: string | null;
|
||||||
|
first_air_date: string;
|
||||||
|
name: string;
|
||||||
|
vote_average: number;
|
||||||
|
vote_count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TMDBShowResponse {
|
||||||
|
page: number;
|
||||||
|
results: TMDBShowResult[];
|
||||||
|
total_pages: number;
|
||||||
|
total_results: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TMDBMovieResult {
|
||||||
|
adult: boolean;
|
||||||
|
backdrop_path: string | null;
|
||||||
|
genre_ids: number[];
|
||||||
|
id: number;
|
||||||
|
original_language: string;
|
||||||
|
original_title: string;
|
||||||
|
overview: string;
|
||||||
|
popularity: number;
|
||||||
|
poster_path: string | null;
|
||||||
|
release_date: string;
|
||||||
|
title: string;
|
||||||
|
video: boolean;
|
||||||
|
vote_average: number;
|
||||||
|
vote_count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TMDBMovieResponse {
|
||||||
|
page: number;
|
||||||
|
results: TMDBMovieResult[];
|
||||||
|
total_pages: number;
|
||||||
|
total_results: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TMDBSearchResultsPromise = Promise<
|
||||||
|
TMDBShowResponse | TMDBMovieResponse
|
||||||
|
>;
|
||||||
|
|
||||||
|
export interface TMDBSearchResultStatic {
|
||||||
|
searchMedia(query: string, type: TMDBContentTypes): TMDBSearchResultsPromise;
|
||||||
|
searchMedia(query: string, type: "movie"): TMDBSearchResultsPromise;
|
||||||
|
searchMedia(query: string, type: "show"): TMDBSearchResultsPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TMDBEpisode {
|
||||||
|
air_date: string;
|
||||||
|
episode_number: number;
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
overview: string;
|
||||||
|
production_code: string;
|
||||||
|
runtime: number;
|
||||||
|
season_number: number;
|
||||||
|
show_id: number;
|
||||||
|
still_path: string | null;
|
||||||
|
vote_average: number;
|
||||||
|
vote_count: number;
|
||||||
|
crew: any[];
|
||||||
|
guest_stars: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TMDBSeason {
|
||||||
|
_id: string;
|
||||||
|
air_date: string;
|
||||||
|
episodes: TMDBEpisode[];
|
||||||
|
name: string;
|
||||||
|
overview: string;
|
||||||
|
id: number;
|
||||||
|
poster_path: string | null;
|
||||||
|
season_number: number;
|
||||||
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
import { TTVMediaToId } from "@/backend/metadata/getmeta";
|
import { TMDBMediaToId } from "@/backend/metadata/getmeta";
|
||||||
import { MWMediaMeta } from "@/backend/metadata/types";
|
import { MWMediaMeta } from "@/backend/metadata/types";
|
||||||
import { DotList } from "@/components/text/DotList";
|
import { DotList } from "@/components/text/DotList";
|
||||||
|
|
||||||
@ -132,7 +132,7 @@ export function MediaCard(props: MediaCardProps) {
|
|||||||
const canLink = props.linkable && !props.closable;
|
const canLink = props.linkable && !props.closable;
|
||||||
|
|
||||||
let link = canLink
|
let link = canLink
|
||||||
? `/media/${encodeURIComponent(TTVMediaToId(props.media))}`
|
? `/media/${encodeURIComponent(TMDBMediaToId(props.media))}`
|
||||||
: "#";
|
: "#";
|
||||||
if (canLink && props.series)
|
if (canLink && props.series)
|
||||||
link += `/${encodeURIComponent(props.series.seasonId)}/${encodeURIComponent(
|
link += `/${encodeURIComponent(props.series.seasonId)}/${encodeURIComponent(
|
||||||
|
@ -2,7 +2,7 @@ import { useCallback, useMemo, useState } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
|
|
||||||
import { decodeTTVId, getMetaFromId } from "@/backend/metadata/getmeta";
|
import { decodeTMDBId, getMetaFromId } from "@/backend/metadata/getmeta";
|
||||||
import { MWMediaType, MWSeasonWithEpisodeMeta } from "@/backend/metadata/types";
|
import { MWMediaType, MWSeasonWithEpisodeMeta } from "@/backend/metadata/types";
|
||||||
import { IconPatch } from "@/components/buttons/IconPatch";
|
import { IconPatch } from "@/components/buttons/IconPatch";
|
||||||
import { Icon, Icons } from "@/components/Icon";
|
import { Icon, Icons } from "@/components/Icon";
|
||||||
@ -44,7 +44,7 @@ export function EpisodeSelectionPopout() {
|
|||||||
seasonId: sId,
|
seasonId: sId,
|
||||||
season: undefined,
|
season: undefined,
|
||||||
});
|
});
|
||||||
reqSeasonMeta(decodeTTVId(params.media)?.id as string, sId).then((v) => {
|
reqSeasonMeta(decodeTMDBId(params.media)?.id as string, sId).then((v) => {
|
||||||
if (v?.meta.type !== MWMediaType.SERIES) return;
|
if (v?.meta.type !== MWMediaType.SERIES) return;
|
||||||
setCurrentVisibleSeason({
|
setCurrentVisibleSeason({
|
||||||
seasonId: sId,
|
seasonId: sId,
|
||||||
|
@ -6,7 +6,7 @@ import { useHistory, useParams } from "react-router-dom";
|
|||||||
import { MWStream } from "@/backend/helpers/streams";
|
import { MWStream } from "@/backend/helpers/streams";
|
||||||
import {
|
import {
|
||||||
DetailedMeta,
|
DetailedMeta,
|
||||||
decodeTTVId,
|
decodeTMDBId,
|
||||||
getMetaFromId,
|
getMetaFromId,
|
||||||
} from "@/backend/metadata/getmeta";
|
} from "@/backend/metadata/getmeta";
|
||||||
import { MWMediaType, MWSeasonWithEpisodeMeta } from "@/backend/metadata/types";
|
import { MWMediaType, MWSeasonWithEpisodeMeta } from "@/backend/metadata/types";
|
||||||
@ -184,7 +184,7 @@ export function MediaView() {
|
|||||||
const [selected, setSelected] = useState<SelectedMediaData | null>(null);
|
const [selected, setSelected] = useState<SelectedMediaData | null>(null);
|
||||||
const [exec, loading, error] = useLoading(
|
const [exec, loading, error] = useLoading(
|
||||||
async (mediaParams: string, seasonId?: string) => {
|
async (mediaParams: string, seasonId?: string) => {
|
||||||
const data = decodeTTVId(mediaParams);
|
const data = decodeTMDBId(mediaParams);
|
||||||
if (!data) return null;
|
if (!data) return null;
|
||||||
return getMetaFromId(data.type, data.id, seasonId);
|
return getMetaFromId(data.type, data.id, seasonId);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user