partial refactor

This commit is contained in:
castdrian 2023-06-12 20:06:46 +02:00
parent ce4721e1bb
commit 40e45ae103
4 changed files with 529 additions and 0 deletions

View 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;
}

View 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,
};
} */
}

View 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;
}
}

View 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;
}