refactor search

This commit is contained in:
castdrian 2023-06-30 12:20:01 +02:00
parent 7c890443e0
commit 95f03db5b2
6 changed files with 45 additions and 162 deletions

View File

@ -19,6 +19,7 @@ import {
} from "./types/justwatch";
import { MWMediaMeta, MWMediaType } from "./types/mw";
import {
TMDBContentTypes,
TMDBMediaResult,
TMDBMovieData,
TMDBSeasonMetaResult,
@ -177,7 +178,7 @@ export async function convertLegacyUrl(
const urlParts = url.split("/").slice(2);
const [, type, id] = urlParts[0].split("-", 3);
const mediaType = TMDBMediaToMediaType(type);
const mediaType = TMDBMediaToMediaType(type as TMDBContentTypes);
const meta = await getLegacyMetaFromId(mediaType, id);
if (!meta) return undefined;

View File

@ -1,11 +1,6 @@
import { SimpleCache } from "@/utils/cache";
import {
formatTMDBMeta,
formatTMDBSearchResult,
mediaTypeToTMDB,
searchMedia,
} from "./tmdb";
import { formatTMDBMeta, formatTMDBSearchResult, multiSearch } from "./tmdb";
import { MWMediaMeta, MWQuery } from "./types/mw";
const cache = new SimpleCache<MWQuery, MWMediaMeta[]>();
@ -16,11 +11,11 @@ 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 { searchQuery } = query;
const data = await searchMedia(searchQuery, mediaTypeToTMDB(type));
const results = data.results.map((v) => {
const formattedResult = formatTMDBSearchResult(v, mediaTypeToTMDB(type));
const data = await multiSearch(searchQuery);
const results = data.map((v) => {
const formattedResult = formatTMDBSearchResult(v, v.media_type);
return formatTMDBMeta(formattedResult);
});

View File

@ -11,29 +11,25 @@ import {
TMDBMediaResult,
TMDBMovieData,
TMDBMovieExternalIds,
TMDBMovieResponse,
TMDBMovieResult,
TMDBMovieSearchResult,
TMDBSearchResult,
TMDBSeason,
TMDBSeasonMetaResult,
TMDBShowData,
TMDBShowExternalIds,
TMDBShowResponse,
TMDBShowResult,
TMDBShowSearchResult,
} from "./types/tmdb";
import { mwFetch } from "../helpers/fetch";
export function mediaTypeToTMDB(type: MWMediaType): TMDBContentTypes {
if (type === MWMediaType.MOVIE) return "movie";
if (type === MWMediaType.SERIES) return "show";
if (type === MWMediaType.MOVIE) return TMDBContentTypes.MOVIE;
if (type === MWMediaType.SERIES) return TMDBContentTypes.TV;
throw new Error("unsupported type");
}
export function TMDBMediaToMediaType(type: string): MWMediaType {
if (type === "movie") return MWMediaType.MOVIE;
if (type === "show") return MWMediaType.SERIES;
export function TMDBMediaToMediaType(type: TMDBContentTypes): MWMediaType {
if (type === TMDBContentTypes.MOVIE) return MWMediaType.MOVIE;
if (type === TMDBContentTypes.TV) return MWMediaType.SERIES;
throw new Error("unsupported type");
}
@ -103,7 +99,7 @@ export function decodeTMDBId(
if (prefix !== "tmdb") return null;
let mediaType;
try {
mediaType = TMDBMediaToMediaType(type);
mediaType = TMDBMediaToMediaType(type as TMDBContentTypes);
} catch {
return null;
}
@ -131,36 +127,6 @@ async function get<T>(url: string, params?: object): Promise<T> {
return res;
}
export async function searchMedia(
query: string,
type: TMDBContentTypes
): Promise<TMDBMovieResponse | TMDBShowResponse> {
let data;
switch (type) {
case "movie":
data = await get<TMDBMovieResponse>("search/movie", {
query,
include_adult: false,
language: "en-US",
page: 1,
});
break;
case "show":
data = await get<TMDBShowResponse>("search/tv", {
query,
include_adult: false,
language: "en-US",
page: 1,
});
break;
default:
throw new Error("Invalid media type");
}
return data;
}
export async function multiSearch(
query: string
): Promise<(TMDBMovieSearchResult | TMDBShowSearchResult)[]> {
@ -172,7 +138,9 @@ export async function multiSearch(
});
// filter out results that aren't movies or shows
const results = data.results.filter(
(r) => r.media_type === "movie" || r.media_type === "tv"
(r) =>
r.media_type === TMDBContentTypes.MOVIE ||
r.media_type === TMDBContentTypes.TV
);
return results;
}
@ -183,31 +151,32 @@ export async function generateQuickSearchMediaUrl(
const data = await multiSearch(query);
if (data.length === 0) return undefined;
const result = data[0];
const type = result.media_type === "movie" ? "movie" : "show";
const title = result.media_type === "movie" ? result.title : result.name;
const title =
result.media_type === TMDBContentTypes.MOVIE ? result.title : result.name;
return `/media/${TMDBIdToUrlId(
TMDBMediaToMediaType(type),
TMDBMediaToMediaType(result.media_type),
result.id.toString(),
title
)}`;
}
// Conditional type which for inferring the return type based on the content type
type MediaDetailReturn<T extends TMDBContentTypes> = T extends "movie"
? TMDBMovieData
: T extends "show"
? TMDBShowData
: never;
type MediaDetailReturn<T extends TMDBContentTypes> =
T extends TMDBContentTypes.MOVIE
? TMDBMovieData
: T extends TMDBContentTypes.TV
? TMDBShowData
: never;
export function getMediaDetails<
T extends TMDBContentTypes,
TReturn = MediaDetailReturn<T>
>(id: string, type: T): Promise<TReturn> {
if (type === "movie") {
if (type === TMDBContentTypes.MOVIE) {
return get<TReturn>(`/movie/${id}`);
}
if (type === "show") {
if (type === TMDBContentTypes.TV) {
return get<TReturn>(`/tv/${id}`);
}
throw new Error("Invalid media type");
@ -236,10 +205,10 @@ export async function getExternalIds(
let data;
switch (type) {
case "movie":
case TMDBContentTypes.MOVIE:
data = await get<TMDBMovieExternalIds>(`/movie/${id}/external_ids`);
break;
case "show":
case TMDBContentTypes.TV:
data = await get<TMDBShowExternalIds>(`/tv/${id}/external_ids`);
break;
default:
@ -263,12 +232,12 @@ export async function getMovieFromExternalId(
}
export function formatTMDBSearchResult(
result: TMDBShowResult | TMDBMovieResult,
result: TMDBMovieSearchResult | TMDBShowSearchResult,
mediatype: TMDBContentTypes
): TMDBMediaResult {
const type = TMDBMediaToMediaType(mediatype);
if (type === MWMediaType.SERIES) {
const show = result as TMDBShowResult;
const show = result as TMDBShowSearchResult;
return {
title: show.name,
poster: getMediaPoster(show.poster_path),
@ -277,7 +246,8 @@ export function formatTMDBSearchResult(
object_type: mediatype,
};
}
const movie = result as TMDBMovieResult;
const movie = result as TMDBMovieSearchResult;
return {
title: movie.title,

View File

@ -1,4 +1,7 @@
export type TMDBContentTypes = "movie" | "show";
export enum TMDBContentTypes {
MOVIE = "movie",
TV = "tv",
}
export type TMDBSeasonShort = {
title: string;
@ -183,54 +186,6 @@ export interface TMDBEpisodeResult {
};
}
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 interface TMDBEpisode {
air_date: string;
episode_number: number;
@ -316,7 +271,7 @@ export interface TMDBMovieSearchResult {
original_title: string;
overview: string;
poster_path: string;
media_type: "movie";
media_type: TMDBContentTypes.MOVIE;
genre_ids: number[];
popularity: number;
release_date: string;
@ -334,7 +289,7 @@ export interface TMDBShowSearchResult {
original_name: string;
overview: string;
poster_path: string;
media_type: "tv";
media_type: TMDBContentTypes.TV;
genre_ids: number[];
popularity: number;
first_air_date: string;

View File

@ -1,14 +1,9 @@
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { MWQuery } from "@/backend/metadata/types/mw";
import { MWMediaType, MWQuery } from "@/backend/metadata/types/mw";
import { DropdownButton } from "./buttons/DropdownButton";
import { Icon, Icons } from "./Icon";
import { TextInputControl } from "./text-inputs/TextInputControl";
export interface SearchBarProps {
buttonText?: string;
placeholder?: string;
onChange: (value: MWQuery, force: boolean) => void;
onUnFocus: () => void;
@ -16,9 +11,6 @@ export interface SearchBarProps {
}
export function SearchBarInput(props: SearchBarProps) {
const { t } = useTranslation();
const [dropdownOpen, setDropdownOpen] = useState(false);
function setSearch(value: string) {
props.onChange(
{
@ -28,15 +20,6 @@ export function SearchBarInput(props: SearchBarProps) {
false
);
}
function setType(type: string) {
props.onChange(
{
...props.value,
type: type as MWMediaType,
},
true
);
}
return (
<div className="relative flex flex-col rounded-[28px] bg-denim-400 transition-colors focus-within:bg-denim-400 hover:bg-denim-500 sm:flex-row sm:items-center">
@ -51,31 +34,6 @@ export function SearchBarInput(props: SearchBarProps) {
className="w-full flex-1 bg-transparent px-4 py-4 pl-12 text-white placeholder-denim-700 focus:outline-none sm:py-4 sm:pr-2"
placeholder={props.placeholder}
/>
<div className="px-4 py-4 pt-0 sm:px-2 sm:py-2">
<DropdownButton
icon={Icons.SEARCH}
open={dropdownOpen}
setOpen={(val) => setDropdownOpen(val)}
selectedItem={props.value.type}
setSelectedItem={(val) => setType(val)}
options={[
{
id: MWMediaType.MOVIE,
name: t("searchBar.movie"),
icon: Icons.FILM,
},
{
id: MWMediaType.SERIES,
name: t("searchBar.series"),
icon: Icons.CLAPPER_BOARD,
},
]}
onClick={() => setDropdownOpen((old) => !old)}
>
{props.buttonText || t("searchBar.search")}
</DropdownButton>
</div>
</div>
);
}

View File

@ -5,6 +5,7 @@ import {
getMovieFromExternalId,
} from "@/backend/metadata/tmdb";
import { MWMediaType } from "@/backend/metadata/types/mw";
import { TMDBContentTypes } from "@/backend/metadata/types/tmdb";
import { BookmarkStoreData } from "@/state/bookmark/types";
import { isNotNull } from "@/utils/typeguard";
@ -59,7 +60,10 @@ export async function migrateV3Videos(
clone.item.meta.id = migratedId;
if (clone.item.series) {
const series = clone.item.series;
const details = await getMediaDetails(migratedId, "show");
const details = await getMediaDetails(
migratedId,
TMDBContentTypes.TV
);
const season = details.seasons.find(
(v) => v.season_number === series.season