mirror of
https://github.com/movie-web/movie-web.git
synced 2024-12-28 01:41:50 +01:00
partial refactor
This commit is contained in:
parent
7e5c2f9b88
commit
70852773f9
21
src/backend/metadata/search_new.ts
Normal file
21
src/backend/metadata/search_new.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { SimpleCache } from "@/utils/cache";
|
||||||
|
|
||||||
|
import { Trakt, mediaTypeToTTV } from "./trakttv";
|
||||||
|
import { MWMediaMeta, MWQuery } from "./types";
|
||||||
|
|
||||||
|
const cache = new SimpleCache<MWQuery, MWMediaMeta[]>();
|
||||||
|
cache.setCompare((a, b) => {
|
||||||
|
return a.type === b.type && a.searchQuery.trim() === b.searchQuery.trim();
|
||||||
|
});
|
||||||
|
cache.initialize();
|
||||||
|
|
||||||
|
export async function searchForMedia(query: MWQuery): Promise<MWMediaMeta[]> {
|
||||||
|
if (cache.has(query)) return cache.get(query) as MWMediaMeta[];
|
||||||
|
const { searchQuery, type } = query;
|
||||||
|
|
||||||
|
const contentType = mediaTypeToTTV(type);
|
||||||
|
|
||||||
|
const results = await Trakt.search(searchQuery, contentType);
|
||||||
|
cache.set(query, results, 3600);
|
||||||
|
return results;
|
||||||
|
}
|
78
src/backend/metadata/tmdb.ts
Normal file
78
src/backend/metadata/tmdb.ts
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import { conf } from "@/setup/config";
|
||||||
|
|
||||||
|
import {
|
||||||
|
DetailedMeta,
|
||||||
|
MWMediaType,
|
||||||
|
TMDBMediaStatic,
|
||||||
|
TMDBMovieData,
|
||||||
|
TMDBShowData,
|
||||||
|
} from "./types";
|
||||||
|
import { mwFetch } from "../helpers/fetch";
|
||||||
|
|
||||||
|
export abstract class Tmdb {
|
||||||
|
private static baseURL = "https://api.themoviedb.org/3";
|
||||||
|
|
||||||
|
private static headers = {
|
||||||
|
accept: "application/json",
|
||||||
|
Authorization: `Bearer ${conf().TMDB_API_KEY}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
private static async get<T>(url: string): Promise<T> {
|
||||||
|
const res = await mwFetch<any>(url, {
|
||||||
|
headers: Tmdb.headers,
|
||||||
|
baseURL: Tmdb.baseURL,
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getMediaDetails: TMDBMediaStatic["getMediaDetails"] = async (
|
||||||
|
id: string,
|
||||||
|
type: MWMediaType
|
||||||
|
) => {
|
||||||
|
let data;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case "movie":
|
||||||
|
data = await Tmdb.get<TMDBMovieData>(`/movie/${id}`);
|
||||||
|
break;
|
||||||
|
case "series":
|
||||||
|
data = await Tmdb.get<TMDBShowData>(`/tv/${id}`);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error("Invalid media type");
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
public static getMediaPoster(posterPath: string | null): string | undefined {
|
||||||
|
if (posterPath) return `https://image.tmdb.org/t/p/w185/${posterPath}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* public static async getMetaFromId(
|
||||||
|
type: MWMediaType,
|
||||||
|
id: string,
|
||||||
|
seasonId?: string
|
||||||
|
): Promise<DetailedMeta | null> {
|
||||||
|
console.log("getMetaFromId", type, id, seasonId);
|
||||||
|
|
||||||
|
const details = await Tmdb.getMediaDetails(id, type);
|
||||||
|
|
||||||
|
if (!details) return null;
|
||||||
|
|
||||||
|
let imdbId;
|
||||||
|
if (type === MWMediaType.MOVIE) {
|
||||||
|
imdbId = (details as TMDBMovieData).imdb_id ?? undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!meta.length) return null;
|
||||||
|
|
||||||
|
console.log(meta);
|
||||||
|
|
||||||
|
return {
|
||||||
|
meta,
|
||||||
|
imdbId,
|
||||||
|
tmdbId: id,
|
||||||
|
};
|
||||||
|
} */
|
||||||
|
}
|
166
src/backend/metadata/trakttv.ts
Normal file
166
src/backend/metadata/trakttv.ts
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
import { conf } from "@/setup/config";
|
||||||
|
|
||||||
|
import { Tmdb } from "./tmdb";
|
||||||
|
import {
|
||||||
|
DetailedMeta,
|
||||||
|
MWMediaMeta,
|
||||||
|
MWMediaType,
|
||||||
|
MWSeasonMeta,
|
||||||
|
TMDBShowData,
|
||||||
|
TTVContentTypes,
|
||||||
|
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 ["TTV", 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 !== "TTV") 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)
|
||||||
|
);
|
||||||
|
console.log(details);
|
||||||
|
|
||||||
|
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.trakt,
|
||||||
|
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 getMetaFromId(
|
||||||
|
type: MWMediaType,
|
||||||
|
id: string,
|
||||||
|
seasonId?: string
|
||||||
|
): Promise<DetailedMeta | null> {
|
||||||
|
console.log("getMetaFromId", type, id, seasonId);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
264
src/backend/metadata/types_new.ts
Normal file
264
src/backend/metadata/types_new.ts
Normal file
@ -0,0 +1,264 @@
|
|||||||
|
export enum MWMediaType {
|
||||||
|
MOVIE = "movie",
|
||||||
|
SERIES = "series",
|
||||||
|
ANIME = "anime",
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MWSeasonMeta = {
|
||||||
|
id: string;
|
||||||
|
number: number;
|
||||||
|
title: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MWSeasonWithEpisodeMeta = {
|
||||||
|
id: string;
|
||||||
|
number: number;
|
||||||
|
title: string;
|
||||||
|
episodes: {
|
||||||
|
id: string;
|
||||||
|
number: number;
|
||||||
|
title: string;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type MWMediaMetaBase = {
|
||||||
|
title: string;
|
||||||
|
id: string;
|
||||||
|
year?: string;
|
||||||
|
poster?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type MWMediaMetaSpecific =
|
||||||
|
| {
|
||||||
|
type: MWMediaType.MOVIE | MWMediaType.ANIME;
|
||||||
|
seasons: undefined;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: MWMediaType.SERIES;
|
||||||
|
seasons: MWSeasonMeta[];
|
||||||
|
seasonData: MWSeasonWithEpisodeMeta;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MWMediaMeta = MWMediaMetaBase & MWMediaMetaSpecific;
|
||||||
|
|
||||||
|
export interface MWQuery {
|
||||||
|
searchQuery: string;
|
||||||
|
type: MWMediaType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TTVContentTypes = "movie" | "show";
|
||||||
|
|
||||||
|
export type TTVSeasonShort = {
|
||||||
|
title: string;
|
||||||
|
id: number;
|
||||||
|
season_number: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TTVEpisodeShort = {
|
||||||
|
title: string;
|
||||||
|
id: number;
|
||||||
|
episode_number: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TTVMediaResult = {
|
||||||
|
title: string;
|
||||||
|
poster?: string;
|
||||||
|
id: number;
|
||||||
|
original_release_year?: number;
|
||||||
|
ttv_entity_id: string;
|
||||||
|
object_type: TTVContentTypes;
|
||||||
|
seasons?: TTVSeasonShort[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TTVSeasonMetaResult = {
|
||||||
|
title: string;
|
||||||
|
id: string;
|
||||||
|
season_number: number;
|
||||||
|
episodes: TTVEpisodeShort[];
|
||||||
|
};
|
||||||
|
|
||||||
|
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 {
|
||||||
|
meta: MWMediaMeta;
|
||||||
|
imdbId?: string;
|
||||||
|
tmdbId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TMDBShowData {
|
||||||
|
adult: boolean;
|
||||||
|
backdrop_path: string | null;
|
||||||
|
created_by: {
|
||||||
|
id: number;
|
||||||
|
credit_id: string;
|
||||||
|
name: string;
|
||||||
|
gender: number;
|
||||||
|
profile_path: string | null;
|
||||||
|
}[];
|
||||||
|
episode_run_time: number[];
|
||||||
|
first_air_date: string;
|
||||||
|
genres: {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
}[];
|
||||||
|
homepage: string;
|
||||||
|
id: number;
|
||||||
|
in_production: boolean;
|
||||||
|
languages: string[];
|
||||||
|
last_air_date: string;
|
||||||
|
last_episode_to_air: {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
overview: string;
|
||||||
|
vote_average: number;
|
||||||
|
vote_count: number;
|
||||||
|
air_date: string;
|
||||||
|
episode_number: number;
|
||||||
|
production_code: string;
|
||||||
|
runtime: number | null;
|
||||||
|
season_number: number;
|
||||||
|
show_id: number;
|
||||||
|
still_path: string | null;
|
||||||
|
} | null;
|
||||||
|
name: string;
|
||||||
|
next_episode_to_air: {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
overview: string;
|
||||||
|
vote_average: number;
|
||||||
|
vote_count: number;
|
||||||
|
air_date: string;
|
||||||
|
episode_number: number;
|
||||||
|
production_code: string;
|
||||||
|
runtime: number | null;
|
||||||
|
season_number: number;
|
||||||
|
show_id: number;
|
||||||
|
still_path: string | null;
|
||||||
|
} | null;
|
||||||
|
networks: {
|
||||||
|
id: number;
|
||||||
|
logo_path: string;
|
||||||
|
name: string;
|
||||||
|
origin_country: string;
|
||||||
|
}[];
|
||||||
|
number_of_episodes: number;
|
||||||
|
number_of_seasons: number;
|
||||||
|
origin_country: string[];
|
||||||
|
original_language: string;
|
||||||
|
original_name: string;
|
||||||
|
overview: string;
|
||||||
|
popularity: number;
|
||||||
|
poster_path: string | null;
|
||||||
|
production_companies: {
|
||||||
|
id: number;
|
||||||
|
logo_path: string | null;
|
||||||
|
name: string;
|
||||||
|
origin_country: string;
|
||||||
|
}[];
|
||||||
|
production_countries: {
|
||||||
|
iso_3166_1: string;
|
||||||
|
name: string;
|
||||||
|
}[];
|
||||||
|
seasons: {
|
||||||
|
air_date: string;
|
||||||
|
episode_count: number;
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
overview: string;
|
||||||
|
poster_path: string | null;
|
||||||
|
season_number: number;
|
||||||
|
}[];
|
||||||
|
spoken_languages: {
|
||||||
|
english_name: string;
|
||||||
|
iso_639_1: string;
|
||||||
|
name: string;
|
||||||
|
}[];
|
||||||
|
status: string;
|
||||||
|
tagline: string;
|
||||||
|
type: string;
|
||||||
|
vote_average: number;
|
||||||
|
vote_count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TMDBMovieData {
|
||||||
|
adult: boolean;
|
||||||
|
backdrop_path: string | null;
|
||||||
|
belongs_to_collection: {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
poster_path: string | null;
|
||||||
|
backdrop_path: string | null;
|
||||||
|
} | null;
|
||||||
|
budget: number;
|
||||||
|
genres: {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
}[];
|
||||||
|
homepage: string | null;
|
||||||
|
id: number;
|
||||||
|
imdb_id: string | null;
|
||||||
|
original_language: string;
|
||||||
|
original_title: string;
|
||||||
|
overview: string | null;
|
||||||
|
popularity: number;
|
||||||
|
poster_path: string | null;
|
||||||
|
production_companies: {
|
||||||
|
id: number;
|
||||||
|
logo_path: string | null;
|
||||||
|
name: string;
|
||||||
|
origin_country: string;
|
||||||
|
}[];
|
||||||
|
production_countries: {
|
||||||
|
iso_3166_1: string;
|
||||||
|
name: string;
|
||||||
|
}[];
|
||||||
|
release_date: string;
|
||||||
|
revenue: number;
|
||||||
|
runtime: number | null;
|
||||||
|
spoken_languages: {
|
||||||
|
english_name: string;
|
||||||
|
iso_639_1: string;
|
||||||
|
name: string;
|
||||||
|
}[];
|
||||||
|
status: string;
|
||||||
|
tagline: string | null;
|
||||||
|
title: string;
|
||||||
|
video: boolean;
|
||||||
|
vote_average: number;
|
||||||
|
vote_count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TMDBMediaDetailsPromise = Promise<TMDBShowData | TMDBMovieData>;
|
||||||
|
|
||||||
|
export interface TMDBMediaStatic {
|
||||||
|
getMediaDetails(
|
||||||
|
id: string,
|
||||||
|
type: MWMediaType.SERIES
|
||||||
|
): TMDBMediaDetailsPromise;
|
||||||
|
getMediaDetails(id: string, type: MWMediaType.MOVIE): TMDBMediaDetailsPromise;
|
||||||
|
getMediaDetails(id: string, type: MWMediaType): TMDBMediaDetailsPromise;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user