diff --git a/src/backend/metadata/getmeta.ts b/src/backend/metadata/getmeta.ts index b2166c34..fe2ea62b 100644 --- a/src/backend/metadata/getmeta.ts +++ b/src/backend/metadata/getmeta.ts @@ -8,6 +8,7 @@ import { getExternalIds, getMediaDetails, getMediaPoster, + getMovieFromExternalId, mediaTypeToTMDB, } from "./tmdb"; import { @@ -206,11 +207,23 @@ export async function convertLegacyUrl( if (url.startsWith("/media/JW")) { const urlParts = url.split("/").slice(2); const [, type, id] = urlParts[0].split("-", 3); - const meta = await getLegacyMetaFromId(TMDBMediaToMediaType(type), id); + + const mediaType = TMDBMediaToMediaType(type); + const meta = await getLegacyMetaFromId(mediaType, id); + if (!meta) return undefined; - const tmdbId = meta.tmdbId; - if (!tmdbId) return undefined; - return `/media/tmdb-${type}-${tmdbId}`; + const { tmdbId, imdbId } = meta; + if (!tmdbId && !imdbId) return undefined; + + // movies always have an imdb id on tmdb + if (imdbId && mediaType === MWMediaType.MOVIE) { + const movieId = await getMovieFromExternalId(imdbId); + if (movieId) return `/media/tmdb-movie-${movieId}`; + } + + if (tmdbId) { + return `/media/tmdb-${type}-${tmdbId}`; + } } return undefined; } diff --git a/src/backend/metadata/tmdb.ts b/src/backend/metadata/tmdb.ts index f5d1e370..db665528 100644 --- a/src/backend/metadata/tmdb.ts +++ b/src/backend/metadata/tmdb.ts @@ -2,6 +2,7 @@ import { conf } from "@/setup/config"; import { MWMediaMeta, MWMediaType, MWSeasonMeta } from "./types/mw"; import { + ExternalIdMovieSearchResult, TMDBContentTypes, TMDBEpisodeShort, TMDBExternalIds, @@ -195,6 +196,19 @@ export async function getExternalIds( return data; } +export async function getMovieFromExternalId( + imdbId: string +): Promise<string | undefined> { + const data = await get<ExternalIdMovieSearchResult>(`/find/${imdbId}`, { + external_source: "imdb_id", + }); + + const movie = data.movie_results[0]; + if (!movie) return undefined; + + return movie.id.toString(); +} + export function formatTMDBSearchResult( result: TMDBShowResult | TMDBMovieResult, mediatype: TMDBContentTypes diff --git a/src/backend/metadata/types/tmdb.ts b/src/backend/metadata/types/tmdb.ts index cb5e9aa4..843786f4 100644 --- a/src/backend/metadata/types/tmdb.ts +++ b/src/backend/metadata/types/tmdb.ts @@ -282,3 +282,27 @@ export interface TMDBMovieExternalIds { } export type TMDBExternalIds = TMDBShowExternalIds | TMDBMovieExternalIds; + +export interface ExternalIdMovieSearchResult { + movie_results: { + adult: boolean; + backdrop_path: string; + id: number; + title: string; + original_language: string; + original_title: string; + overview: string; + poster_path: string; + media_type: string; + genre_ids: number[]; + popularity: number; + release_date: string; + video: boolean; + vote_average: number; + vote_count: number; + }[]; + person_results: any[]; + tv_results: any[]; + tv_episode_results: any[]; + tv_season_results: any[]; +} diff --git a/src/setup/App.tsx b/src/setup/App.tsx index d0a0887f..0516eb48 100644 --- a/src/setup/App.tsx +++ b/src/setup/App.tsx @@ -1,11 +1,5 @@ -import { lazy } from "react"; -import { - Redirect, - Route, - Switch, - useHistory, - useLocation, -} from "react-router-dom"; +import { lazy, useEffect, useState } from "react"; +import { Redirect, Route, Switch, useLocation } from "react-router-dom"; import { convertLegacyUrl } from "@/backend/metadata/getmeta"; import { MWMediaType } from "@/backend/metadata/types/mw"; @@ -19,86 +13,105 @@ import { NotFoundPage } from "@/views/notfound/NotFoundView"; import { V2MigrationView } from "@/views/other/v2Migration"; import { SearchView } from "@/views/search/SearchView"; -function App() { +// eslint-disable-next-line react/function-component-definition, react/prop-types +const LegacyUrlView: React.FC = ({ children }) => { const location = useLocation(); - const history = useHistory(); + const [redirectUrl, setRedirectUrl] = useState<string | null>(null); - // Call the conversion function and redirect if necessary - convertLegacyUrl(location.pathname).then((convertedUrl) => { - if (convertedUrl) { - history.replace(convertedUrl); - } - }); + useEffect(() => { + // Call the conversion function and set the redirect URL if necessary + convertLegacyUrl(location.pathname).then((convertedUrl) => { + if (convertedUrl) { + setRedirectUrl(convertedUrl); + } + }); + }, [location.pathname]); + + if (redirectUrl) { + return <Redirect to={redirectUrl} />; + } + + // eslint-disable-next-line react/jsx-no-useless-fragment + return <>{children}</>; +}; + +function App() { return ( <SettingsProvider> <WatchedContextProvider> <BookmarkContextProvider> <BannerContextProvider> <Layout> - <Switch> - {/* functional routes */} - <Route exact path="/v2-migration" component={V2MigrationView} /> - <Route exact path="/"> - <Redirect to={`/search/${MWMediaType.MOVIE}`} /> - </Route> + <LegacyUrlView> + <Switch> + {/* functional routes */} + <Route + exact + path="/v2-migration" + component={V2MigrationView} + /> + <Route exact path="/"> + <Redirect to={`/search/${MWMediaType.MOVIE}`} /> + </Route> - {/* pages */} - <Route exact path="/media/:media" component={MediaView} /> - <Route - exact - path="/media/:media/:season/:episode" - component={MediaView} - /> - <Route - exact - path="/search/:type/:query?" - component={SearchView} - /> + {/* pages */} + <Route exact path="/media/:media" component={MediaView} /> + <Route + exact + path="/media/:media/:season/:episode" + component={MediaView} + /> + <Route + exact + path="/search/:type/:query?" + component={SearchView} + /> - {/* other */} - <Route - exact - path="/dev" - component={lazy( - () => import("@/views/developer/DeveloperView") - )} - /> - <Route - exact - path="/dev/video" - component={lazy( - () => import("@/views/developer/VideoTesterView") - )} - /> - {/* developer routes that can abuse workers are disabled in production */} - {process.env.NODE_ENV === "development" ? ( - <> - <Route - exact - path="/dev/test" - component={lazy( - () => import("@/views/developer/TestView") - )} - /> + {/* other */} + <Route + exact + path="/dev" + component={lazy( + () => import("@/views/developer/DeveloperView") + )} + /> + <Route + exact + path="/dev/video" + component={lazy( + () => import("@/views/developer/VideoTesterView") + )} + /> + {/* developer routes that can abuse workers are disabled in production */} + {process.env.NODE_ENV === "development" ? ( + <> + <Route + exact + path="/dev/test" + component={lazy( + () => import("@/views/developer/TestView") + )} + /> - <Route - exact - path="/dev/providers" - component={lazy( - () => import("@/views/developer/ProviderTesterView") - )} - /> - <Route - exact - path="/dev/embeds" - component={lazy( - () => import("@/views/developer/EmbedTesterView") - )} - /> - </> - ) : null} - <Route path="*" component={NotFoundPage} /> - </Switch> + <Route + exact + path="/dev/providers" + component={lazy( + () => import("@/views/developer/ProviderTesterView") + )} + /> + <Route + exact + path="/dev/embeds" + component={lazy( + () => import("@/views/developer/EmbedTesterView") + )} + /> + </> + ) : null} + <Route path="*" component={NotFoundPage} /> + </Switch> + </LegacyUrlView> </Layout> </BannerContextProvider> </BookmarkContextProvider>