From 430b9564ab3224176b42baa5e13d1216f033cc15 Mon Sep 17 00:00:00 2001 From: adrifcastr Date: Fri, 23 Jun 2023 09:35:07 +0200 Subject: [PATCH 1/6] remove duplicate code --- package.json | 1 + src/backend/metadata/getmeta.ts | 21 ------------------- src/components/media/MediaCard.tsx | 2 +- .../popouts/EpisodeSelectionPopout.tsx | 3 ++- src/views/media/MediaView.tsx | 7 ++----- yarn.lock | 5 +++++ 6 files changed, 11 insertions(+), 28 deletions(-) diff --git a/package.json b/package.json index 3f7c4bf9..0aff05cf 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "react-stickynode": "^4.1.0", "react-transition-group": "^4.4.5", "react-use": "^17.4.0", + "slugify": "^1.6.6", "subsrt-ts": "^2.1.1", "unpacker": "^1.0.1" }, diff --git a/src/backend/metadata/getmeta.ts b/src/backend/metadata/getmeta.ts index c09d8292..a0fea637 100644 --- a/src/backend/metadata/getmeta.ts +++ b/src/backend/metadata/getmeta.ts @@ -180,27 +180,6 @@ export async function getLegacyMetaFromId( }; } -export function TMDBMediaToId(media: MWMediaMeta): string { - return ["tmdb", mediaTypeToTMDB(media.type), media.id].join("-"); -} - -export function decodeTMDBId( - paramId: string -): { id: string; type: MWMediaType } | null { - const [prefix, type, id] = paramId.split("-", 3); - if (prefix !== "tmdb") return null; - let mediaType; - try { - mediaType = TMDBMediaToMediaType(type); - } catch { - return null; - } - return { - type: mediaType, - id, - }; -} - export function isLegacyUrl(url: string): boolean { if (url.startsWith("/media/JW")) return true; return false; diff --git a/src/components/media/MediaCard.tsx b/src/components/media/MediaCard.tsx index a153d8b4..e05fbebb 100644 --- a/src/components/media/MediaCard.tsx +++ b/src/components/media/MediaCard.tsx @@ -1,7 +1,7 @@ import { useTranslation } from "react-i18next"; import { Link } from "react-router-dom"; -import { TMDBMediaToId } from "@/backend/metadata/getmeta"; +import { TMDBMediaToId } from "@/backend/metadata/tmdb"; import { MWMediaMeta } from "@/backend/metadata/types/mw"; import { DotList } from "@/components/text/DotList"; diff --git a/src/video/components/popouts/EpisodeSelectionPopout.tsx b/src/video/components/popouts/EpisodeSelectionPopout.tsx index 66c9ae49..a315a7d7 100644 --- a/src/video/components/popouts/EpisodeSelectionPopout.tsx +++ b/src/video/components/popouts/EpisodeSelectionPopout.tsx @@ -2,7 +2,8 @@ import { useCallback, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { useParams } from "react-router-dom"; -import { decodeTMDBId, getMetaFromId } from "@/backend/metadata/getmeta"; +import { getMetaFromId } from "@/backend/metadata/getmeta"; +import { decodeTMDBId } from "@/backend/metadata/tmdb"; import { MWMediaType, MWSeasonWithEpisodeMeta, diff --git a/src/views/media/MediaView.tsx b/src/views/media/MediaView.tsx index 6e1659a6..ada4f9f8 100644 --- a/src/views/media/MediaView.tsx +++ b/src/views/media/MediaView.tsx @@ -4,11 +4,8 @@ import { useTranslation } from "react-i18next"; import { useHistory, useParams } from "react-router-dom"; import { MWStream } from "@/backend/helpers/streams"; -import { - DetailedMeta, - decodeTMDBId, - getMetaFromId, -} from "@/backend/metadata/getmeta"; +import { DetailedMeta, getMetaFromId } from "@/backend/metadata/getmeta"; +import { decodeTMDBId } from "@/backend/metadata/tmdb"; import { MWMediaType, MWSeasonWithEpisodeMeta, diff --git a/yarn.lock b/yarn.lock index a811afd3..5d687f2a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4764,6 +4764,11 @@ slice-ansi@^5.0.0: ansi-styles "^6.0.0" is-fullwidth-code-point "^4.0.0" +slugify@^1.6.6: + version "1.6.6" + resolved "https://registry.yarnpkg.com/slugify/-/slugify-1.6.6.tgz#2d4ac0eacb47add6af9e04d3be79319cbcc7924b" + integrity sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw== + source-map-js@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" From 7ee1c137604394378260c0fe0f55c991d5f59766 Mon Sep 17 00:00:00 2001 From: adrifcastr Date: Fri, 23 Jun 2023 10:23:46 +0200 Subject: [PATCH 2/6] human readable urls --- src/backend/metadata/getmeta.ts | 12 ++++++++++-- src/backend/metadata/tmdb.ts | 9 ++++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/backend/metadata/getmeta.ts b/src/backend/metadata/getmeta.ts index a0fea637..8e9ab5e3 100644 --- a/src/backend/metadata/getmeta.ts +++ b/src/backend/metadata/getmeta.ts @@ -1,4 +1,5 @@ import { FetchError } from "ofetch"; +import slugify from "slugify"; import { formatJWMeta, mediaTypeToJW } from "./justwatch"; import { @@ -203,10 +204,17 @@ export async function convertLegacyUrl( // 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 (movieId) + return `/media/tmdb-movie-${movieId}-${slugify(meta.meta.title, { + lower: true, + strict: true, + })}`; } if (tmdbId) { - return `/media/tmdb-${type}-${tmdbId}`; + return `/media/tmdb-${type}-${tmdbId}-${slugify(meta.meta.title, { + lower: true, + strict: true, + })}`; } } diff --git a/src/backend/metadata/tmdb.ts b/src/backend/metadata/tmdb.ts index 1c442028..1caa2b55 100644 --- a/src/backend/metadata/tmdb.ts +++ b/src/backend/metadata/tmdb.ts @@ -1,3 +1,5 @@ +import slugify from "slugify"; + import { conf } from "@/setup/config"; import { MWMediaMeta, MWMediaType, MWSeasonMeta } from "./types/mw"; @@ -75,7 +77,12 @@ export function formatTMDBMeta( } export function TMDBMediaToId(media: MWMediaMeta): string { - return ["tmdb", mediaTypeToTMDB(media.type), media.id].join("-"); + return [ + "tmdb", + mediaTypeToTMDB(media.type), + media.id, + slugify(media.title, { lower: true, strict: true }), + ].join("-"); } export function decodeTMDBId( From 517ef2f8cde98368eced2ed2ad82cd4726424ef3 Mon Sep 17 00:00:00 2001 From: adrifcastr Date: Fri, 23 Jun 2023 11:05:01 +0200 Subject: [PATCH 3/6] implement quicksearch --- src/backend/metadata/tmdb.ts | 34 +++++++++++++++++++++++ src/backend/metadata/types/tmdb.ts | 43 ++++++++++++++++++++++++++++++ src/setup/App.tsx | 22 +++++++++++++++ 3 files changed, 99 insertions(+) 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 */} From bfa9638d0eef8aa766525a49f91235b56d3fcf5f Mon Sep 17 00:00:00 2001 From: Jordaar <69628820+Jordaar@users.noreply.github.com> Date: Mon, 26 Jun 2023 19:16:29 +0530 Subject: [PATCH 4/6] feat(metadata): change `window.meta` --- src/video/components/actions/MetaAction.tsx | 44 ++++++++------------- 1 file changed, 17 insertions(+), 27 deletions(-) diff --git a/src/video/components/actions/MetaAction.tsx b/src/video/components/actions/MetaAction.tsx index db5ad6b4..710cbed2 100644 --- a/src/video/components/actions/MetaAction.tsx +++ b/src/video/components/actions/MetaAction.tsx @@ -1,29 +1,21 @@ import { useEffect } from "react"; -import { MWCaption } from "@/backend/helpers/streams"; -import { DetailedMeta } from "@/backend/metadata/getmeta"; import { useVideoPlayerDescriptor } from "@/video/state/hooks"; +import { + VideoMediaPlayingEvent, + useMediaPlaying, +} from "@/video/state/logic/mediaplaying"; import { useMeta } from "@/video/state/logic/meta"; -import { useProgress } from "@/video/state/logic/progress"; +import { VideoProgressEvent, useProgress } from "@/video/state/logic/progress"; +import { VideoPlayerMeta } from "@/video/state/types"; export type WindowMeta = { - meta: DetailedMeta; - captions: MWCaption[]; - episode?: { - episodeId: string; - seasonId: string; + media: VideoPlayerMeta; + state: { + mediaPlaying: VideoMediaPlayingEvent; + progress: VideoProgressEvent; }; - seasons?: { - id: string; - number: number; - title: string; - episodes?: { id: string; number: number; title: string }[]; - }[]; - progress: { - time: number; - duration: number; - }; -} | null; +}; declare global { interface Window { @@ -35,18 +27,16 @@ export function MetaAction() { const descriptor = useVideoPlayerDescriptor(); const meta = useMeta(descriptor); const progress = useProgress(descriptor); + const mediaPlaying = useMediaPlaying(descriptor); useEffect(() => { if (!window.meta) window.meta = {}; if (meta) { window.meta[descriptor] = { - meta: meta.meta, - captions: meta.captions, - seasons: meta.seasons, - episode: meta.episode, - progress: { - time: progress.time, - duration: progress.duration, + media: meta, + state: { + mediaPlaying, + progress, }, }; } @@ -54,7 +44,7 @@ export function MetaAction() { return () => { if (window.meta) delete window.meta[descriptor]; }; - }, [meta, descriptor, progress]); + }, [meta, descriptor, mediaPlaying, progress]); return null; } From e5be04f5ae9110adf1fc628e78b0c6ba60b05c01 Mon Sep 17 00:00:00 2001 From: castdrian Date: Thu, 29 Jun 2023 21:10:17 +0200 Subject: [PATCH 5/6] move forgotten typedefs --- src/backend/metadata/getmeta.ts | 19 +------------------ src/backend/metadata/types/justwatch.ts | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/backend/metadata/getmeta.ts b/src/backend/metadata/getmeta.ts index bb75d90e..ff83c743 100644 --- a/src/backend/metadata/getmeta.ts +++ b/src/backend/metadata/getmeta.ts @@ -13,7 +13,7 @@ import { mediaTypeToTMDB, } from "./tmdb"; import { - JWMediaResult, + JWDetailedMeta, JWSeasonMetaResult, JW_API_BASE, } from "./types/justwatch"; @@ -26,23 +26,6 @@ import { } from "./types/tmdb"; import { makeUrl, proxiedFetch } from "../helpers/fetch"; -type JWExternalIdType = - | "eidr" - | "imdb_latest" - | "imdb" - | "tmdb_latest" - | "tmdb" - | "tms"; - -interface JWExternalId { - provider: JWExternalIdType; - external_id: string; -} - -interface JWDetailedMeta extends JWMediaResult { - external_ids: JWExternalId[]; -} - export interface DetailedMeta { meta: MWMediaMeta; imdbId?: string; diff --git a/src/backend/metadata/types/justwatch.ts b/src/backend/metadata/types/justwatch.ts index cb3ac092..b55e9e24 100644 --- a/src/backend/metadata/types/justwatch.ts +++ b/src/backend/metadata/types/justwatch.ts @@ -46,3 +46,20 @@ export type JWSeasonMetaResult = { season_number: number; episodes: JWEpisodeShort[]; }; + +export type JWExternalIdType = + | "eidr" + | "imdb_latest" + | "imdb" + | "tmdb_latest" + | "tmdb" + | "tms"; + +export interface JWExternalId { + provider: JWExternalIdType; + external_id: string; +} + +export interface JWDetailedMeta extends JWMediaResult { + external_ids: JWExternalId[]; +} From 545ac8bb7bd21ef8071bd9f7af5d3a59d44f85b1 Mon Sep 17 00:00:00 2001 From: castdrian Date: Thu, 29 Jun 2023 21:21:24 +0200 Subject: [PATCH 6/6] reduce code duplication --- src/backend/metadata/getmeta.ts | 19 +++++++------------ src/backend/metadata/tmdb.ts | 25 +++++++++++++++++-------- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/src/backend/metadata/getmeta.ts b/src/backend/metadata/getmeta.ts index ff83c743..6081e5ad 100644 --- a/src/backend/metadata/getmeta.ts +++ b/src/backend/metadata/getmeta.ts @@ -1,8 +1,8 @@ import { FetchError } from "ofetch"; -import slugify from "slugify"; import { formatJWMeta, mediaTypeToJW } from "./justwatch"; import { + TMDBIdToUrlId, TMDBMediaToMediaType, formatTMDBMeta, getEpisodes, @@ -187,17 +187,12 @@ export async function convertLegacyUrl( // 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}-${slugify(meta.meta.title, { - lower: true, - strict: true, - })}`; - } + if (movieId) { + return `/media/${TMDBIdToUrlId(mediaType, movieId, meta.meta.title)}`; + } - if (tmdbId) { - return `/media/tmdb-${type}-${tmdbId}-${slugify(meta.meta.title, { - lower: true, - strict: true, - })}`; + if (tmdbId) { + return `/media/${TMDBIdToUrlId(mediaType, tmdbId, meta.meta.title)}`; + } } } diff --git a/src/backend/metadata/tmdb.ts b/src/backend/metadata/tmdb.ts index 596dd96d..64304900 100644 --- a/src/backend/metadata/tmdb.ts +++ b/src/backend/metadata/tmdb.ts @@ -79,15 +79,23 @@ export function formatTMDBMeta( }; } -export function TMDBMediaToId(media: MWMediaMeta): string { +export function TMDBIdToUrlId( + type: MWMediaType, + tmdbId: string, + title: string +) { return [ "tmdb", - mediaTypeToTMDB(media.type), - media.id, - slugify(media.title, { lower: true, strict: true }), + mediaTypeToTMDB(type), + tmdbId, + slugify(title, { lower: true, strict: true }), ].join("-"); } +export function TMDBMediaToId(media: MWMediaMeta): string { + return TMDBIdToUrlId(media.type, media.id, media.title); +} + export function decodeTMDBId( paramId: string ): { id: string; type: MWMediaType } | null { @@ -178,10 +186,11 @@ export async function generateQuickSearchMediaUrl( 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, - })}`; + return `/media/${TMDBIdToUrlId( + TMDBMediaToMediaType(type), + result.id.toString(), + title + )}`; } // Conditional type which for inferring the return type based on the content type