diff --git a/src/backend/metadata/tmdb.ts b/src/backend/metadata/tmdb.ts index 1caa2b55..20612bd0 100644 --- a/src/backend/metadata/tmdb.ts +++ b/src/backend/metadata/tmdb.ts @@ -13,12 +13,15 @@ import { TMDBMovieExternalIds, TMDBMovieResponse, TMDBMovieResult, + TMDBMovieSearchResult, + TMDBSearchResult, TMDBSeason, TMDBSeasonMetaResult, TMDBShowData, TMDBShowExternalIds, TMDBShowResponse, TMDBShowResult, + TMDBShowSearchResult, } from "./types/tmdb"; import { mwFetch } from "../helpers/fetch"; @@ -150,6 +153,37 @@ export async function searchMedia( return data; } +export async function multiSearch( + query: string +): Promise<(TMDBMovieSearchResult | TMDBShowSearchResult)[]> { + const data = await get(`search/multi`, { + query, + include_adult: false, + language: "en-US", + page: 1, + }); + // filter out results that aren't movies or shows + const results = data.results.filter( + (r) => r.media_type === "movie" || r.media_type === "tv" + ); + return results; +} + +export async function generateQuickSearchMediaUrl( + query: string +): Promise { + 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; + + return `/media/tmdb-${type}-${result.id}-${slugify(title, { + lower: true, + strict: true, + })}`; +} + // Conditional type which for inferring the return type based on the content type type MediaDetailReturn = T extends "movie" ? TMDBMovieData diff --git a/src/backend/metadata/types/tmdb.ts b/src/backend/metadata/types/tmdb.ts index 843786f4..8f6bf14b 100644 --- a/src/backend/metadata/types/tmdb.ts +++ b/src/backend/metadata/types/tmdb.ts @@ -306,3 +306,46 @@ export interface ExternalIdMovieSearchResult { tv_episode_results: any[]; tv_season_results: any[]; } + +export interface TMDBMovieSearchResult { + adult: boolean; + backdrop_path: string; + id: number; + title: string; + original_language: string; + original_title: string; + overview: string; + poster_path: string; + media_type: "movie"; + genre_ids: number[]; + popularity: number; + release_date: string; + video: boolean; + vote_average: number; + vote_count: number; +} + +export interface TMDBShowSearchResult { + adult: boolean; + backdrop_path: string; + id: number; + name: string; + original_language: string; + original_name: string; + overview: string; + poster_path: string; + media_type: "tv"; + genre_ids: number[]; + popularity: number; + first_air_date: string; + vote_average: number; + vote_count: number; + origin_country: string[]; +} + +export interface TMDBSearchResult { + page: number; + results: (TMDBMovieSearchResult | TMDBShowSearchResult)[]; + total_pages: number; + total_results: number; +} diff --git a/src/setup/App.tsx b/src/setup/App.tsx index 7d1847ae..53f3c131 100644 --- a/src/setup/App.tsx +++ b/src/setup/App.tsx @@ -5,9 +5,11 @@ import { Switch, useHistory, useLocation, + useParams, } from "react-router-dom"; import { convertLegacyUrl, isLegacyUrl } from "@/backend/metadata/getmeta"; +import { generateQuickSearchMediaUrl } from "@/backend/metadata/tmdb"; import { MWMediaType } from "@/backend/metadata/types/mw"; import { BannerContextProvider } from "@/hooks/useBanner"; import { Layout } from "@/setup/Layout"; @@ -35,6 +37,23 @@ function LegacyUrlView({ children }: { children: ReactElement }) { return children; } +function QuickSearch() { + const { query } = useParams<{ query: string }>(); + const { replace } = useHistory(); + + useEffect(() => { + if (query) { + generateQuickSearchMediaUrl(query).then((url) => { + replace(url ?? "/"); + }); + } else { + replace("/"); + } + }, [query, replace]); + + return null; +} + function App() { return ( @@ -48,6 +67,9 @@ function App() { + + + {/* pages */}