diff --git a/package.json b/package.json
index 43436852..3228d02f 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "movie-web",
- "version": "3.0.15",
+ "version": "4.0.0",
"private": true,
"homepage": "https://movie-web.app",
"dependencies": {
diff --git a/src/__tests__/providers/providers.test.ts b/src/__tests__/providers/providers.test.ts
index 35c77d5d..350d4255 100644
--- a/src/__tests__/providers/providers.test.ts
+++ b/src/__tests__/providers/providers.test.ts
@@ -4,7 +4,7 @@ import "@/backend";
import { testData } from "@/__tests__/providers/testdata";
import { getProviders } from "@/backend/helpers/register";
import { runProvider } from "@/backend/helpers/run";
-import { MWMediaType } from "@/backend/metadata/types";
+import { MWMediaType } from "@/backend/metadata/types/mw";
describe("providers", () => {
const providers = getProviders();
diff --git a/src/__tests__/providers/testdata.ts b/src/__tests__/providers/testdata.ts
index 37e63e06..6db686e3 100644
--- a/src/__tests__/providers/testdata.ts
+++ b/src/__tests__/providers/testdata.ts
@@ -1,5 +1,5 @@
import { DetailedMeta } from "@/backend/metadata/getmeta";
-import { MWMediaType } from "@/backend/metadata/types";
+import { MWMediaType } from "@/backend/metadata/types/mw";
export const testData: DetailedMeta[] = [
{
diff --git a/src/backend/helpers/provider.ts b/src/backend/helpers/provider.ts
index 6eed4560..58dea7d4 100644
--- a/src/backend/helpers/provider.ts
+++ b/src/backend/helpers/provider.ts
@@ -1,7 +1,7 @@
import { MWEmbed } from "./embed";
import { MWStream } from "./streams";
import { DetailedMeta } from "../metadata/getmeta";
-import { MWMediaType } from "../metadata/types";
+import { MWMediaType } from "../metadata/types/mw";
export type MWProviderScrapeResult = {
stream?: MWStream;
diff --git a/src/backend/helpers/scrape.ts b/src/backend/helpers/scrape.ts
index 70e20348..5f1a100c 100644
--- a/src/backend/helpers/scrape.ts
+++ b/src/backend/helpers/scrape.ts
@@ -3,7 +3,7 @@ import { getEmbedScraperByType, getProviders } from "./register";
import { runEmbedScraper, runProvider } from "./run";
import { MWStream } from "./streams";
import { DetailedMeta } from "../metadata/getmeta";
-import { MWMediaType } from "../metadata/types";
+import { MWMediaType } from "../metadata/types/mw";
interface MWProgressData {
type: "embed" | "provider";
diff --git a/src/backend/metadata/getmeta.ts b/src/backend/metadata/getmeta.ts
index 6b3b9a30..c09d8292 100644
--- a/src/backend/metadata/getmeta.ts
+++ b/src/backend/metadata/getmeta.ts
@@ -1,13 +1,28 @@
import { FetchError } from "ofetch";
+import { formatJWMeta, mediaTypeToJW } from "./justwatch";
+import {
+ TMDBMediaToMediaType,
+ formatTMDBMeta,
+ getEpisodes,
+ getExternalIds,
+ getMediaDetails,
+ getMediaPoster,
+ getMovieFromExternalId,
+ mediaTypeToTMDB,
+} from "./tmdb";
import {
JWMediaResult,
JWSeasonMetaResult,
JW_API_BASE,
- formatJWMeta,
- mediaTypeToJW,
-} from "./justwatch";
-import { MWMediaMeta, MWMediaType } from "./types";
+} from "./types/justwatch";
+import { MWMediaMeta, MWMediaType } from "./types/mw";
+import {
+ TMDBMediaResult,
+ TMDBMovieData,
+ TMDBSeasonMetaResult,
+ TMDBShowData,
+} from "./types/tmdb";
import { makeUrl, proxiedFetch } from "../helpers/fetch";
type JWExternalIdType =
@@ -33,10 +48,92 @@ export interface DetailedMeta {
tmdbId?: string;
}
+export function formatTMDBMetaResult(
+ details: TMDBShowData | TMDBMovieData,
+ type: MWMediaType
+): TMDBMediaResult {
+ if (type === MWMediaType.MOVIE) {
+ const movie = details as TMDBMovieData;
+ return {
+ id: details.id,
+ title: movie.title,
+ object_type: mediaTypeToTMDB(type),
+ poster: getMediaPoster(movie.poster_path) ?? undefined,
+ original_release_year: new Date(movie.release_date).getFullYear(),
+ };
+ }
+ if (type === MWMediaType.SERIES) {
+ const show = details as TMDBShowData;
+ return {
+ id: details.id,
+ title: show.name,
+ object_type: mediaTypeToTMDB(type),
+ seasons: show.seasons.map((v) => ({
+ id: v.id,
+ season_number: v.season_number,
+ title: v.name,
+ })),
+ poster: (details as TMDBMovieData).poster_path ?? undefined,
+ original_release_year: new Date(show.first_air_date).getFullYear(),
+ };
+ }
+
+ throw new Error("unsupported type");
+}
+
export async function getMetaFromId(
type: MWMediaType,
id: string,
seasonId?: string
+): Promise {
+ const details = await getMediaDetails(id, mediaTypeToTMDB(type));
+
+ if (!details) return null;
+
+ const externalIds = await getExternalIds(id, mediaTypeToTMDB(type));
+ const imdbId = externalIds.imdb_id ?? undefined;
+
+ let seasonData: TMDBSeasonMetaResult | undefined;
+
+ if (type === MWMediaType.SERIES) {
+ const seasons = (details as TMDBShowData).seasons;
+
+ let selectedSeason = seasons.find((v) => v.id.toString() === seasonId);
+ if (!selectedSeason) {
+ selectedSeason = seasons.find((v) => v.season_number === 1);
+ }
+
+ if (selectedSeason) {
+ const episodes = await getEpisodes(
+ details.id.toString(),
+ selectedSeason.season_number
+ );
+
+ seasonData = {
+ id: selectedSeason.id.toString(),
+ season_number: selectedSeason.season_number,
+ title: selectedSeason.name,
+ episodes,
+ };
+ }
+ }
+
+ const tmdbmeta = formatTMDBMetaResult(details, type);
+ if (!tmdbmeta) return null;
+ const meta = formatTMDBMeta(tmdbmeta, seasonData);
+ if (!meta) return null;
+
+ return {
+ meta,
+ imdbId,
+ tmdbId: id,
+ };
+}
+
+export async function getLegacyMetaFromId(
+ type: MWMediaType,
+ id: string,
+ seasonId?: string
): Promise {
const queryType = mediaTypeToJW(type);
@@ -82,3 +179,55 @@ export async function getMetaFromId(
tmdbId,
};
}
+
+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;
+}
+
+export async function convertLegacyUrl(
+ url: string
+): Promise {
+ if (!isLegacyUrl(url)) return undefined;
+
+ const urlParts = url.split("/").slice(2);
+ const [, type, id] = urlParts[0].split("-", 3);
+
+ const mediaType = TMDBMediaToMediaType(type);
+ const meta = await getLegacyMetaFromId(mediaType, id);
+
+ if (!meta) return undefined;
+ 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}`;
+ }
+}
diff --git a/src/backend/metadata/justwatch.ts b/src/backend/metadata/justwatch.ts
index 5c79c1e3..724c4acf 100644
--- a/src/backend/metadata/justwatch.ts
+++ b/src/backend/metadata/justwatch.ts
@@ -1,38 +1,10 @@
-import { MWMediaMeta, MWMediaType, MWSeasonMeta } from "./types";
-
-export const JW_API_BASE = "https://apis.justwatch.com";
-export const JW_IMAGE_BASE = "https://images.justwatch.com";
-
-export type JWContentTypes = "movie" | "show";
-
-export type JWSeasonShort = {
- title: string;
- id: number;
- season_number: number;
-};
-
-export type JWEpisodeShort = {
- title: string;
- id: number;
- episode_number: number;
-};
-
-export type JWMediaResult = {
- title: string;
- poster?: string;
- id: number;
- original_release_year?: number;
- jw_entity_id: string;
- object_type: JWContentTypes;
- seasons?: JWSeasonShort[];
-};
-
-export type JWSeasonMetaResult = {
- title: string;
- id: string;
- season_number: number;
- episodes: JWEpisodeShort[];
-};
+import {
+ JWContentTypes,
+ JWMediaResult,
+ JWSeasonMetaResult,
+ JW_IMAGE_BASE,
+} from "./types/justwatch";
+import { MWMediaMeta, MWMediaType, MWSeasonMeta } from "./types/mw";
export function mediaTypeToJW(type: MWMediaType): JWContentTypes {
if (type === MWMediaType.MOVIE) return "movie";
diff --git a/src/backend/metadata/search.ts b/src/backend/metadata/search.ts
index 10cbb285..0d8f561f 100644
--- a/src/backend/metadata/search.ts
+++ b/src/backend/metadata/search.ts
@@ -1,14 +1,12 @@
import { SimpleCache } from "@/utils/cache";
import {
- JWContentTypes,
- JWMediaResult,
- JW_API_BASE,
- formatJWMeta,
- mediaTypeToJW,
-} from "./justwatch";
-import { MWMediaMeta, MWQuery } from "./types";
-import { proxiedFetch } from "../helpers/fetch";
+ formatTMDBMeta,
+ formatTMDBSearchResult,
+ mediaTypeToTMDB,
+ searchMedia,
+} from "./tmdb";
+import { MWMediaMeta, MWQuery } from "./types/mw";
const cache = new SimpleCache();
cache.setCompare((a, b) => {
@@ -16,44 +14,16 @@ cache.setCompare((a, b) => {
});
cache.initialize();
-type JWSearchQuery = {
- content_types: JWContentTypes[];
- page: number;
- page_size: number;
- query: string;
-};
-
-type JWPage = {
- items: T[];
- page: number;
- page_size: number;
- total_pages: number;
- total_results: number;
-};
-
export async function searchForMedia(query: MWQuery): Promise {
if (cache.has(query)) return cache.get(query) as MWMediaMeta[];
const { searchQuery, type } = query;
- const contentType = mediaTypeToJW(type);
- const body: JWSearchQuery = {
- content_types: [contentType],
- page: 1,
- query: searchQuery,
- page_size: 40,
- };
+ const data = await searchMedia(searchQuery, mediaTypeToTMDB(type));
+ const results = data.results.map((v) => {
+ const formattedResult = formatTMDBSearchResult(v, mediaTypeToTMDB(type));
+ return formatTMDBMeta(formattedResult);
+ });
- const data = await proxiedFetch>(
- "/content/titles/en_US/popular",
- {
- baseURL: JW_API_BASE,
- params: {
- body: JSON.stringify(body),
- },
- }
- );
-
- const returnData = data.items.map((v) => formatJWMeta(v));
- cache.set(query, returnData, 3600); // cache for an hour
- return returnData;
+ cache.set(query, results, 3600); // cache results for 1 hour
+ return results;
}
diff --git a/src/backend/metadata/tmdb.ts b/src/backend/metadata/tmdb.ts
new file mode 100644
index 00000000..db665528
--- /dev/null
+++ b/src/backend/metadata/tmdb.ts
@@ -0,0 +1,236 @@
+import { conf } from "@/setup/config";
+
+import { MWMediaMeta, MWMediaType, MWSeasonMeta } from "./types/mw";
+import {
+ ExternalIdMovieSearchResult,
+ TMDBContentTypes,
+ TMDBEpisodeShort,
+ TMDBExternalIds,
+ TMDBMediaResult,
+ TMDBMovieData,
+ TMDBMovieExternalIds,
+ TMDBMovieResponse,
+ TMDBMovieResult,
+ TMDBSeason,
+ TMDBSeasonMetaResult,
+ TMDBShowData,
+ TMDBShowExternalIds,
+ TMDBShowResponse,
+ TMDBShowResult,
+} 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";
+ throw new Error("unsupported type");
+}
+
+export function TMDBMediaToMediaType(type: string): MWMediaType {
+ if (type === "movie") return MWMediaType.MOVIE;
+ if (type === "show") return MWMediaType.SERIES;
+ throw new Error("unsupported type");
+}
+
+export function formatTMDBMeta(
+ media: TMDBMediaResult,
+ season?: TMDBSeasonMetaResult
+): MWMediaMeta {
+ const type = TMDBMediaToMediaType(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 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,
+ };
+}
+
+const baseURL = "https://api.themoviedb.org/3";
+
+const headers = {
+ accept: "application/json",
+ Authorization: `Bearer ${conf().TMDB_API_KEY}`,
+};
+
+async function get(url: string, params?: object): Promise {
+ const res = await mwFetch(encodeURI(url), {
+ headers,
+ baseURL,
+ params: {
+ ...params,
+ },
+ });
+ return res;
+}
+
+export async function searchMedia(
+ query: string,
+ type: TMDBContentTypes
+): Promise {
+ let data;
+
+ switch (type) {
+ case "movie":
+ data = await get("search/movie", {
+ query,
+ include_adult: false,
+ language: "en-US",
+ page: 1,
+ });
+ break;
+ case "show":
+ data = await get("search/tv", {
+ query,
+ include_adult: false,
+ language: "en-US",
+ page: 1,
+ });
+ break;
+ default:
+ throw new Error("Invalid media type");
+ }
+
+ return data;
+}
+
+export async function getMediaDetails(id: string, type: TMDBContentTypes) {
+ let data;
+
+ switch (type) {
+ case "movie":
+ data = await get(`/movie/${id}`);
+ break;
+ case "show":
+ data = await get(`/tv/${id}`);
+ break;
+ default:
+ throw new Error("Invalid media type");
+ }
+
+ return data;
+}
+
+export function getMediaPoster(posterPath: string | null): string | undefined {
+ if (posterPath) return `https://image.tmdb.org/t/p/w185/${posterPath}`;
+}
+
+export async function getEpisodes(
+ id: string,
+ season: number
+): Promise {
+ const data = await get(`/tv/${id}/season/${season}`);
+ return data.episodes.map((e) => ({
+ id: e.id,
+ episode_number: e.episode_number,
+ title: e.name,
+ }));
+}
+
+export async function getExternalIds(
+ id: string,
+ type: TMDBContentTypes
+): Promise {
+ let data;
+
+ switch (type) {
+ case "movie":
+ data = await get(`/movie/${id}/external_ids`);
+ break;
+ case "show":
+ data = await get(`/tv/${id}/external_ids`);
+ break;
+ default:
+ throw new Error("Invalid media type");
+ }
+
+ return data;
+}
+
+export async function getMovieFromExternalId(
+ imdbId: string
+): Promise {
+ const data = await get(`/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
+): TMDBMediaResult {
+ const type = TMDBMediaToMediaType(mediatype);
+ if (type === MWMediaType.SERIES) {
+ const show = result as TMDBShowResult;
+ return {
+ title: show.name,
+ poster: getMediaPoster(show.poster_path),
+ id: show.id,
+ original_release_year: new Date(show.first_air_date).getFullYear(),
+ object_type: mediatype,
+ };
+ }
+ const movie = result as TMDBMovieResult;
+
+ return {
+ title: movie.title,
+ poster: getMediaPoster(movie.poster_path),
+ id: movie.id,
+ original_release_year: new Date(movie.release_date).getFullYear(),
+ object_type: mediatype,
+ };
+}
diff --git a/src/backend/metadata/types/justwatch.ts b/src/backend/metadata/types/justwatch.ts
new file mode 100644
index 00000000..cb3ac092
--- /dev/null
+++ b/src/backend/metadata/types/justwatch.ts
@@ -0,0 +1,48 @@
+export type JWContentTypes = "movie" | "show";
+
+export type JWSearchQuery = {
+ content_types: JWContentTypes[];
+ page: number;
+ page_size: number;
+ query: string;
+};
+
+export type JWPage = {
+ items: T[];
+ page: number;
+ page_size: number;
+ total_pages: number;
+ total_results: number;
+};
+
+export const JW_API_BASE = "https://apis.justwatch.com";
+export const JW_IMAGE_BASE = "https://images.justwatch.com";
+
+export type JWSeasonShort = {
+ title: string;
+ id: number;
+ season_number: number;
+};
+
+export type JWEpisodeShort = {
+ title: string;
+ id: number;
+ episode_number: number;
+};
+
+export type JWMediaResult = {
+ title: string;
+ poster?: string;
+ id: number;
+ original_release_year?: number;
+ jw_entity_id: string;
+ object_type: JWContentTypes;
+ seasons?: JWSeasonShort[];
+};
+
+export type JWSeasonMetaResult = {
+ title: string;
+ id: string;
+ season_number: number;
+ episodes: JWEpisodeShort[];
+};
diff --git a/src/backend/metadata/types.ts b/src/backend/metadata/types/mw.ts
similarity index 89%
rename from src/backend/metadata/types.ts
rename to src/backend/metadata/types/mw.ts
index 2723fbe7..e7cc26fe 100644
--- a/src/backend/metadata/types.ts
+++ b/src/backend/metadata/types/mw.ts
@@ -45,3 +45,9 @@ export interface MWQuery {
searchQuery: string;
type: MWMediaType;
}
+
+export interface DetailedMeta {
+ meta: MWMediaMeta;
+ imdbId?: string;
+ tmdbId?: string;
+}
diff --git a/src/backend/metadata/types/tmdb.ts b/src/backend/metadata/types/tmdb.ts
new file mode 100644
index 00000000..843786f4
--- /dev/null
+++ b/src/backend/metadata/types/tmdb.ts
@@ -0,0 +1,308 @@
+export type TMDBContentTypes = "movie" | "show";
+
+export type TMDBSeasonShort = {
+ title: string;
+ id: number;
+ season_number: number;
+};
+
+export type TMDBEpisodeShort = {
+ title: string;
+ id: number;
+ episode_number: number;
+};
+
+export type TMDBMediaResult = {
+ title: string;
+ poster?: string;
+ id: number;
+ original_release_year?: number;
+ object_type: TMDBContentTypes;
+ seasons?: TMDBSeasonShort[];
+};
+
+export type TMDBSeasonMetaResult = {
+ title: string;
+ id: string;
+ season_number: number;
+ episodes: TMDBEpisodeShort[];
+};
+
+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 interface TMDBEpisodeResult {
+ season: number;
+ number: number;
+ title: string;
+ ids: {
+ trakt: number;
+ tvdb: number;
+ imdb: string;
+ tmdb: number;
+ };
+}
+
+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;
+ id: number;
+ name: string;
+ overview: string;
+ production_code: string;
+ runtime: number;
+ season_number: number;
+ show_id: number;
+ still_path: string | null;
+ vote_average: number;
+ vote_count: number;
+ crew: any[];
+ guest_stars: any[];
+}
+
+export interface TMDBSeason {
+ _id: string;
+ air_date: string;
+ episodes: TMDBEpisode[];
+ name: string;
+ overview: string;
+ id: number;
+ poster_path: string | null;
+ season_number: number;
+}
+
+export interface TMDBShowExternalIds {
+ id: number;
+ imdb_id: null | string;
+ freebase_mid: null | string;
+ freebase_id: null | string;
+ tvdb_id: number;
+ tvrage_id: null | string;
+ wikidata_id: null | string;
+ facebook_id: null | string;
+ instagram_id: null | string;
+ twitter_id: null | string;
+}
+
+export interface TMDBMovieExternalIds {
+ id: number;
+ imdb_id: null | string;
+ wikidata_id: null | string;
+ facebook_id: null | string;
+ instagram_id: null | string;
+ twitter_id: null | string;
+}
+
+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/backend/providers/2embed.ts b/src/backend/providers/2embed.ts
index 7cc8938e..507d5a2d 100644
--- a/src/backend/providers/2embed.ts
+++ b/src/backend/providers/2embed.ts
@@ -8,7 +8,7 @@ import {
MWStreamQuality,
MWStreamType,
} from "../helpers/streams";
-import { MWMediaType } from "../metadata/types";
+import { MWMediaType } from "../metadata/types/mw";
const twoEmbedBase = "https://www.2embed.to";
diff --git a/src/backend/providers/flixhq.ts b/src/backend/providers/flixhq.ts
index 376abd08..fd905019 100644
--- a/src/backend/providers/flixhq.ts
+++ b/src/backend/providers/flixhq.ts
@@ -7,7 +7,7 @@ import {
import { mwFetch } from "../helpers/fetch";
import { registerProvider } from "../helpers/register";
import { MWCaption, MWStreamQuality, MWStreamType } from "../helpers/streams";
-import { MWMediaType } from "../metadata/types";
+import { MWMediaType } from "../metadata/types/mw";
const flixHqBase = "https://consumet-api-clone.vercel.app/meta/tmdb"; // instance stolen from streaminal :)
diff --git a/src/backend/providers/gdriveplayer.ts b/src/backend/providers/gdriveplayer.ts
index 5478b6ed..c184fea7 100644
--- a/src/backend/providers/gdriveplayer.ts
+++ b/src/backend/providers/gdriveplayer.ts
@@ -3,7 +3,7 @@ import { unpack } from "unpacker";
import { registerProvider } from "@/backend/helpers/register";
import { MWStreamQuality } from "@/backend/helpers/streams";
-import { MWMediaType } from "@/backend/metadata/types";
+import { MWMediaType } from "@/backend/metadata/types/mw";
import { proxiedFetch } from "../helpers/fetch";
diff --git a/src/backend/providers/gomovies.ts b/src/backend/providers/gomovies.ts
index 9e22d095..fdce289b 100644
--- a/src/backend/providers/gomovies.ts
+++ b/src/backend/providers/gomovies.ts
@@ -1,7 +1,7 @@
import { MWEmbedType } from "../helpers/embed";
import { proxiedFetch } from "../helpers/fetch";
import { registerProvider } from "../helpers/register";
-import { MWMediaType } from "../metadata/types";
+import { MWMediaType } from "../metadata/types/mw";
const gomoviesBase = "https://gomovies.sx";
diff --git a/src/backend/providers/hdwatched.ts b/src/backend/providers/hdwatched.ts
index 2096e160..458c3424 100644
--- a/src/backend/providers/hdwatched.ts
+++ b/src/backend/providers/hdwatched.ts
@@ -2,7 +2,7 @@ import { proxiedFetch } from "../helpers/fetch";
import { MWProviderContext } from "../helpers/provider";
import { registerProvider } from "../helpers/register";
import { MWStreamQuality, MWStreamType } from "../helpers/streams";
-import { MWMediaType } from "../metadata/types";
+import { MWMediaType } from "../metadata/types/mw";
const hdwatchedBase = "https://www.hdwatched.xyz";
diff --git a/src/backend/providers/kissasian.ts b/src/backend/providers/kissasian.ts
index 90708970..a95e05ab 100644
--- a/src/backend/providers/kissasian.ts
+++ b/src/backend/providers/kissasian.ts
@@ -1,7 +1,7 @@
import { MWEmbedType } from "../helpers/embed";
import { proxiedFetch } from "../helpers/fetch";
import { registerProvider } from "../helpers/register";
-import { MWMediaType } from "../metadata/types";
+import { MWMediaType } from "../metadata/types/mw";
const kissasianBase = "https://kissasian.li";
diff --git a/src/backend/providers/m4ufree.ts b/src/backend/providers/m4ufree.ts
index 0fe5303d..b9d5aef0 100644
--- a/src/backend/providers/m4ufree.ts
+++ b/src/backend/providers/m4ufree.ts
@@ -2,7 +2,7 @@ import { MWEmbed, MWEmbedType } from "@/backend/helpers/embed";
import { proxiedFetch } from "../helpers/fetch";
import { registerProvider } from "../helpers/register";
-import { MWMediaType } from "../metadata/types";
+import { MWMediaType } from "../metadata/types/mw";
const HOST = "m4ufree.com";
const URL_BASE = `https://${HOST}`;
diff --git a/src/backend/providers/netfilm.ts b/src/backend/providers/netfilm.ts
index f7efcfbe..54016733 100644
--- a/src/backend/providers/netfilm.ts
+++ b/src/backend/providers/netfilm.ts
@@ -5,7 +5,7 @@ import {
MWStreamQuality,
MWStreamType,
} from "../helpers/streams";
-import { MWMediaType } from "../metadata/types";
+import { MWMediaType } from "../metadata/types/mw";
const netfilmBase = "https://net-film.vercel.app";
diff --git a/src/backend/providers/remotestream.ts b/src/backend/providers/remotestream.ts
index 02c0f199..093069e8 100644
--- a/src/backend/providers/remotestream.ts
+++ b/src/backend/providers/remotestream.ts
@@ -1,7 +1,7 @@
import { mwFetch } from "@/backend/helpers/fetch";
import { registerProvider } from "@/backend/helpers/register";
import { MWStreamQuality, MWStreamType } from "@/backend/helpers/streams";
-import { MWMediaType } from "@/backend/metadata/types";
+import { MWMediaType } from "@/backend/metadata/types/mw";
const remotestreamBase = `https://fsa.remotestre.am`;
diff --git a/src/backend/providers/sflix.ts b/src/backend/providers/sflix.ts
index 4121046b..2cb1c598 100644
--- a/src/backend/providers/sflix.ts
+++ b/src/backend/providers/sflix.ts
@@ -1,7 +1,7 @@
import { proxiedFetch } from "../helpers/fetch";
import { registerProvider } from "../helpers/register";
import { MWStreamQuality, MWStreamType } from "../helpers/streams";
-import { MWMediaType } from "../metadata/types";
+import { MWMediaType } from "../metadata/types/mw";
const sflixBase = "https://sflix.video";
diff --git a/src/backend/providers/streamflix.ts b/src/backend/providers/streamflix.ts
index 90dd4975..d4488b03 100644
--- a/src/backend/providers/streamflix.ts
+++ b/src/backend/providers/streamflix.ts
@@ -5,7 +5,7 @@ import {
MWStreamQuality,
MWStreamType,
} from "@/backend/helpers/streams";
-import { MWMediaType } from "@/backend/metadata/types";
+import { MWMediaType } from "@/backend/metadata/types/mw";
const streamflixBase = "https://us-west2-compute-proxied.streamflix.one";
diff --git a/src/backend/providers/superstream/index.ts b/src/backend/providers/superstream/index.ts
index 585d8d8a..75a8b844 100644
--- a/src/backend/providers/superstream/index.ts
+++ b/src/backend/providers/superstream/index.ts
@@ -13,7 +13,7 @@ import {
MWStreamQuality,
MWStreamType,
} from "@/backend/helpers/streams";
-import { MWMediaType } from "@/backend/metadata/types";
+import { MWMediaType } from "@/backend/metadata/types/mw";
import { compareTitle } from "@/utils/titleMatch";
const nanoid = customAlphabet("0123456789abcdef", 32);
diff --git a/src/components/SearchBar.tsx b/src/components/SearchBar.tsx
index 4940cbc7..431de337 100644
--- a/src/components/SearchBar.tsx
+++ b/src/components/SearchBar.tsx
@@ -1,7 +1,7 @@
import { useState } from "react";
import { useTranslation } from "react-i18next";
-import { MWMediaType, MWQuery } from "@/backend/metadata/types";
+import { MWMediaType, MWQuery } from "@/backend/metadata/types/mw";
import { DropdownButton } from "./buttons/DropdownButton";
import { Icon, Icons } from "./Icon";
diff --git a/src/components/media/MediaCard.tsx b/src/components/media/MediaCard.tsx
index 22865717..a153d8b4 100644
--- a/src/components/media/MediaCard.tsx
+++ b/src/components/media/MediaCard.tsx
@@ -1,8 +1,8 @@
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
-import { JWMediaToId } from "@/backend/metadata/justwatch";
-import { MWMediaMeta } from "@/backend/metadata/types";
+import { TMDBMediaToId } from "@/backend/metadata/getmeta";
+import { MWMediaMeta } from "@/backend/metadata/types/mw";
import { DotList } from "@/components/text/DotList";
import { IconPatch } from "../buttons/IconPatch";
@@ -13,7 +13,7 @@ export interface MediaCardProps {
linkable?: boolean;
series?: {
episode: number;
- season: number;
+ season?: number;
episodeId: string;
seasonId: string;
};
@@ -72,7 +72,7 @@ function MediaCardContent({
].join(" ")}
>
{t("seasons.seasonAndEpisode", {
- season: series.season,
+ season: series.season || 1,
episode: series.episode,
})}
@@ -132,12 +132,17 @@ export function MediaCard(props: MediaCardProps) {
const canLink = props.linkable && !props.closable;
let link = canLink
- ? `/media/${encodeURIComponent(JWMediaToId(props.media))}`
+ ? `/media/${encodeURIComponent(TMDBMediaToId(props.media))}`
: "#";
- if (canLink && props.series)
- link += `/${encodeURIComponent(props.series.seasonId)}/${encodeURIComponent(
- props.series.episodeId
- )}`;
+ if (canLink && props.series) {
+ if (props.series.season === 0 && !props.series.episodeId) {
+ link += `/${encodeURIComponent(props.series.seasonId)}`;
+ } else {
+ link += `/${encodeURIComponent(
+ props.series.seasonId
+ )}/${encodeURIComponent(props.series.episodeId)}`;
+ }
+ }
if (!props.linkable) return {content};
return (
diff --git a/src/components/media/WatchedMediaCard.tsx b/src/components/media/WatchedMediaCard.tsx
index 346c77b6..ade1612a 100644
--- a/src/components/media/WatchedMediaCard.tsx
+++ b/src/components/media/WatchedMediaCard.tsx
@@ -1,6 +1,6 @@
import { useMemo } from "react";
-import { MWMediaMeta } from "@/backend/metadata/types";
+import { MWMediaMeta } from "@/backend/metadata/types/mw";
import { useWatchedContext } from "@/state/watched";
import { MediaCard } from "./MediaCard";
diff --git a/src/hooks/useScrape.ts b/src/hooks/useScrape.ts
index a375e618..3cffa4ee 100644
--- a/src/hooks/useScrape.ts
+++ b/src/hooks/useScrape.ts
@@ -3,7 +3,7 @@ import { useEffect, useState } from "react";
import { findBestStream } from "@/backend/helpers/scrape";
import { MWStream } from "@/backend/helpers/streams";
import { DetailedMeta } from "@/backend/metadata/getmeta";
-import { MWMediaType } from "@/backend/metadata/types";
+import { MWMediaType } from "@/backend/metadata/types/mw";
export interface ScrapeEventLog {
type: "provider" | "embed";
diff --git a/src/hooks/useSearchQuery.ts b/src/hooks/useSearchQuery.ts
index d431a0d0..cb8c3171 100644
--- a/src/hooks/useSearchQuery.ts
+++ b/src/hooks/useSearchQuery.ts
@@ -1,7 +1,7 @@
import { useState } from "react";
import { generatePath, useHistory, useRouteMatch } from "react-router-dom";
-import { MWMediaType, MWQuery } from "@/backend/metadata/types";
+import { MWMediaType, MWQuery } from "@/backend/metadata/types/mw";
function getInitialValue(params: { type: string; query: string }) {
const type =
diff --git a/src/setup/App.tsx b/src/setup/App.tsx
index 992549e0..7d1847ae 100644
--- a/src/setup/App.tsx
+++ b/src/setup/App.tsx
@@ -1,7 +1,14 @@
-import { lazy } from "react";
-import { Redirect, Route, Switch } from "react-router-dom";
+import { ReactElement, lazy, useEffect } from "react";
+import {
+ Redirect,
+ Route,
+ Switch,
+ useHistory,
+ useLocation,
+} from "react-router-dom";
-import { MWMediaType } from "@/backend/metadata/types";
+import { convertLegacyUrl, isLegacyUrl } from "@/backend/metadata/getmeta";
+import { MWMediaType } from "@/backend/metadata/types/mw";
import { BannerContextProvider } from "@/hooks/useBanner";
import { Layout } from "@/setup/Layout";
import { BookmarkContextProvider } from "@/state/bookmark";
@@ -12,6 +19,22 @@ import { NotFoundPage } from "@/views/notfound/NotFoundView";
import { V2MigrationView } from "@/views/other/v2Migration";
import { SearchView } from "@/views/search/SearchView";
+function LegacyUrlView({ children }: { children: ReactElement }) {
+ const location = useLocation();
+ const { replace } = useHistory();
+
+ useEffect(() => {
+ const url = location.pathname;
+ if (!isLegacyUrl(url)) return;
+ convertLegacyUrl(location.pathname).then((convertedUrl) => {
+ replace(convertedUrl ?? "/");
+ });
+ }, [location.pathname, replace]);
+
+ if (isLegacyUrl(location.pathname)) return null;
+ return children;
+}
+
function App() {
return (
@@ -27,12 +50,16 @@ function App() {
{/* pages */}
-
-
+
+
+
+
+
+
+
+
+
+
()
.setKey("mw-bookmarks")
@@ -13,6 +14,12 @@ export const BookmarkStore = createVersionedStore()
})
.addVersion({
version: 1,
+ migrate(old: OldBookmarks) {
+ return migrateV2Bookmarks(old);
+ },
+ })
+ .addVersion({
+ version: 2,
create() {
return {
bookmarks: [],
diff --git a/src/state/bookmark/types.ts b/src/state/bookmark/types.ts
index 05cb3641..79b92a5c 100644
--- a/src/state/bookmark/types.ts
+++ b/src/state/bookmark/types.ts
@@ -1,4 +1,4 @@
-import { MWMediaMeta } from "@/backend/metadata/types";
+import { MWMediaMeta } from "@/backend/metadata/types/mw";
export interface BookmarkStoreData {
bookmarks: MWMediaMeta[];
diff --git a/src/state/watched/context.tsx b/src/state/watched/context.tsx
index 3ce17b2a..661b0ed3 100644
--- a/src/state/watched/context.tsx
+++ b/src/state/watched/context.tsx
@@ -8,7 +8,7 @@ import {
} from "react";
import { DetailedMeta } from "@/backend/metadata/getmeta";
-import { MWMediaType } from "@/backend/metadata/types";
+import { MWMediaType } from "@/backend/metadata/types/mw";
import { useStore } from "@/utils/storage";
import { VideoProgressStore } from "./store";
diff --git a/src/state/watched/migrations/v2.ts b/src/state/watched/migrations/v2.ts
index 8f7a56b6..94f1141b 100644
--- a/src/state/watched/migrations/v2.ts
+++ b/src/state/watched/migrations/v2.ts
@@ -1,6 +1,6 @@
import { DetailedMeta, getMetaFromId } from "@/backend/metadata/getmeta";
import { searchForMedia } from "@/backend/metadata/search";
-import { MWMediaMeta, MWMediaType } from "@/backend/metadata/types";
+import { MWMediaMeta, MWMediaType } from "@/backend/metadata/types/mw";
import { compareTitle } from "@/utils/titleMatch";
import { WatchedStoreData, WatchedStoreItem } from "../types";
diff --git a/src/state/watched/migrations/v3.ts b/src/state/watched/migrations/v3.ts
new file mode 100644
index 00000000..71e0b182
--- /dev/null
+++ b/src/state/watched/migrations/v3.ts
@@ -0,0 +1,81 @@
+import { getLegacyMetaFromId } from "@/backend/metadata/getmeta";
+import { getMovieFromExternalId } from "@/backend/metadata/tmdb";
+import { MWMediaType } from "@/backend/metadata/types/mw";
+
+import { WatchedStoreData } from "../types";
+
+async function migrateId(
+ id: number,
+ type: MWMediaType
+): Promise {
+ const meta = await getLegacyMetaFromId(type, id.toString());
+
+ if (!meta) return undefined;
+ const { tmdbId, imdbId } = meta;
+ if (!tmdbId && !imdbId) return undefined;
+
+ // movies always have an imdb id on tmdb
+ if (imdbId && type === MWMediaType.MOVIE) {
+ const movieId = await getMovieFromExternalId(imdbId);
+ if (movieId) return movieId;
+ }
+
+ if (tmdbId) {
+ return tmdbId;
+ }
+}
+
+export async function migrateV2Bookmarks(old: any) {
+ const oldData = old;
+ if (!oldData) return;
+
+ const updatedBookmarks = oldData.bookmarks.map(
+ async (item: { id: number; type: MWMediaType }) => ({
+ ...item,
+ id: await migrateId(item.id, item.type),
+ })
+ );
+
+ return {
+ bookmarks: (await Promise.all(updatedBookmarks)).filter((item) => item.id),
+ };
+}
+
+export async function migrateV3Videos(old: any) {
+ const oldData = old;
+ if (!oldData) return;
+
+ const updatedItems = await Promise.all(
+ oldData.items.map(async (item: any) => {
+ const migratedId = await migrateId(
+ item.item.meta.id,
+ item.item.meta.type
+ );
+
+ const migratedItem = {
+ ...item,
+ item: {
+ ...item.item,
+ meta: {
+ ...item.item.meta,
+ id: migratedId,
+ },
+ },
+ };
+
+ return {
+ ...item,
+ item: migratedId ? migratedItem : item.item,
+ };
+ })
+ );
+
+ const newData: WatchedStoreData = {
+ items: updatedItems.map((item) => item.item),
+ };
+
+ return {
+ ...oldData,
+ items: newData.items,
+ };
+}
diff --git a/src/state/watched/store.ts b/src/state/watched/store.ts
index 95adef28..b59c37dc 100644
--- a/src/state/watched/store.ts
+++ b/src/state/watched/store.ts
@@ -1,6 +1,7 @@
import { createVersionedStore } from "@/utils/storage";
import { OldData, migrateV2Videos } from "./migrations/v2";
+import { migrateV3Videos } from "./migrations/v3";
import { WatchedStoreData } from "./types";
export const VideoProgressStore = createVersionedStore()
@@ -21,6 +22,12 @@ export const VideoProgressStore = createVersionedStore()
})
.addVersion({
version: 2,
+ migrate(old: OldData) {
+ return migrateV3Videos(old);
+ },
+ })
+ .addVersion({
+ version: 3,
create() {
return {
items: [],
diff --git a/src/state/watched/types.ts b/src/state/watched/types.ts
index a3246c38..0854b90b 100644
--- a/src/state/watched/types.ts
+++ b/src/state/watched/types.ts
@@ -1,4 +1,4 @@
-import { MWMediaMeta } from "@/backend/metadata/types";
+import { MWMediaMeta } from "@/backend/metadata/types/mw";
export interface StoreMediaItem {
meta: MWMediaMeta;
diff --git a/src/video/components/actions/DividerAction.tsx b/src/video/components/actions/DividerAction.tsx
index 5778e16f..3aeaeaef 100644
--- a/src/video/components/actions/DividerAction.tsx
+++ b/src/video/components/actions/DividerAction.tsx
@@ -1,4 +1,4 @@
-import { MWMediaType } from "@/backend/metadata/types";
+import { MWMediaType } from "@/backend/metadata/types/mw";
import { useVideoPlayerDescriptor } from "@/video/state/hooks";
import { useMeta } from "@/video/state/logic/meta";
diff --git a/src/video/components/actions/SeriesSelectionAction.tsx b/src/video/components/actions/SeriesSelectionAction.tsx
index d228b047..9eff0bb6 100644
--- a/src/video/components/actions/SeriesSelectionAction.tsx
+++ b/src/video/components/actions/SeriesSelectionAction.tsx
@@ -1,6 +1,6 @@
import { useTranslation } from "react-i18next";
-import { MWMediaType } from "@/backend/metadata/types";
+import { MWMediaType } from "@/backend/metadata/types/mw";
import { Icons } from "@/components/Icon";
import { FloatingAnchor } from "@/components/popout/FloatingAnchor";
import { VideoPlayerIconButton } from "@/video/components/parts/VideoPlayerIconButton";
diff --git a/src/video/components/controllers/MetaController.tsx b/src/video/components/controllers/MetaController.tsx
index ee6bc696..25757e25 100644
--- a/src/video/components/controllers/MetaController.tsx
+++ b/src/video/components/controllers/MetaController.tsx
@@ -1,7 +1,7 @@
import { useEffect } from "react";
import { MWCaption } from "@/backend/helpers/streams";
-import { MWSeasonWithEpisodeMeta } from "@/backend/metadata/types";
+import { MWSeasonWithEpisodeMeta } from "@/backend/metadata/types/mw";
import { useVideoPlayerDescriptor } from "@/video/state/hooks";
import { useControls } from "@/video/state/logic/controls";
import { VideoPlayerMeta } from "@/video/state/types";
diff --git a/src/video/components/hooks/useCurrentSeriesEpisodeInfo.ts b/src/video/components/hooks/useCurrentSeriesEpisodeInfo.ts
index 6eb51170..11dfdc88 100644
--- a/src/video/components/hooks/useCurrentSeriesEpisodeInfo.ts
+++ b/src/video/components/hooks/useCurrentSeriesEpisodeInfo.ts
@@ -1,7 +1,7 @@
import { useMemo } from "react";
import { useTranslation } from "react-i18next";
-import { MWMediaType } from "@/backend/metadata/types";
+import { MWMediaType } from "@/backend/metadata/types/mw";
import { useMeta } from "@/video/state/logic/meta";
export function useCurrentSeriesEpisodeInfo(descriptor: string) {
diff --git a/src/video/components/parts/VideoErrorBoundary.tsx b/src/video/components/parts/VideoErrorBoundary.tsx
index 5786aa7a..061bf2b7 100644
--- a/src/video/components/parts/VideoErrorBoundary.tsx
+++ b/src/video/components/parts/VideoErrorBoundary.tsx
@@ -2,7 +2,7 @@ import { Component } from "react";
import { Trans } from "react-i18next";
import type { ReactNode } from "react-router-dom/node_modules/@types/react/index";
-import { MWMediaMeta } from "@/backend/metadata/types";
+import { MWMediaMeta } from "@/backend/metadata/types/mw";
import { ErrorMessage } from "@/components/layout/ErrorBoundary";
import { Link } from "@/components/text/Link";
import { conf } from "@/setup/config";
diff --git a/src/video/components/parts/VideoPlayerHeader.tsx b/src/video/components/parts/VideoPlayerHeader.tsx
index 8c026c49..3a333ee3 100644
--- a/src/video/components/parts/VideoPlayerHeader.tsx
+++ b/src/video/components/parts/VideoPlayerHeader.tsx
@@ -1,6 +1,6 @@
import { useTranslation } from "react-i18next";
-import { MWMediaMeta } from "@/backend/metadata/types";
+import { MWMediaMeta } from "@/backend/metadata/types/mw";
import { IconPatch } from "@/components/buttons/IconPatch";
import { Icon, Icons } from "@/components/Icon";
import { BrandPill } from "@/components/layout/BrandPill";
diff --git a/src/video/components/popouts/EpisodeSelectionPopout.tsx b/src/video/components/popouts/EpisodeSelectionPopout.tsx
index c80045bd..66c9ae49 100644
--- a/src/video/components/popouts/EpisodeSelectionPopout.tsx
+++ b/src/video/components/popouts/EpisodeSelectionPopout.tsx
@@ -2,9 +2,11 @@ import { useCallback, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { useParams } from "react-router-dom";
-import { getMetaFromId } from "@/backend/metadata/getmeta";
-import { decodeJWId } from "@/backend/metadata/justwatch";
-import { MWMediaType, MWSeasonWithEpisodeMeta } from "@/backend/metadata/types";
+import { decodeTMDBId, getMetaFromId } from "@/backend/metadata/getmeta";
+import {
+ MWMediaType,
+ MWSeasonWithEpisodeMeta,
+} from "@/backend/metadata/types/mw";
import { IconPatch } from "@/components/buttons/IconPatch";
import { Icon, Icons } from "@/components/Icon";
import { Loading } from "@/components/layout/Loading";
@@ -45,7 +47,7 @@ export function EpisodeSelectionPopout() {
seasonId: sId,
season: undefined,
});
- reqSeasonMeta(decodeJWId(params.media)?.id as string, sId).then((v) => {
+ reqSeasonMeta(decodeTMDBId(params.media)?.id as string, sId).then((v) => {
if (v?.meta.type !== MWMediaType.SERIES) return;
setCurrentVisibleSeason({
seasonId: sId,
diff --git a/src/views/developer/VideoTesterView.tsx b/src/views/developer/VideoTesterView.tsx
index b192cd40..e1b3fa35 100644
--- a/src/views/developer/VideoTesterView.tsx
+++ b/src/views/developer/VideoTesterView.tsx
@@ -3,7 +3,7 @@ import { Helmet } from "react-helmet";
import { MWStreamQuality, MWStreamType } from "@/backend/helpers/streams";
import { DetailedMeta } from "@/backend/metadata/getmeta";
-import { MWMediaType } from "@/backend/metadata/types";
+import { MWMediaType } from "@/backend/metadata/types/mw";
import { Button } from "@/components/Button";
import { Dropdown } from "@/components/Dropdown";
import { Navigation } from "@/components/layout/Navigation";
diff --git a/src/views/media/MediaView.tsx b/src/views/media/MediaView.tsx
index c55211c7..6e1659a6 100644
--- a/src/views/media/MediaView.tsx
+++ b/src/views/media/MediaView.tsx
@@ -4,9 +4,15 @@ import { useTranslation } from "react-i18next";
import { useHistory, useParams } from "react-router-dom";
import { MWStream } from "@/backend/helpers/streams";
-import { DetailedMeta, getMetaFromId } from "@/backend/metadata/getmeta";
-import { decodeJWId } from "@/backend/metadata/justwatch";
-import { MWMediaType, MWSeasonWithEpisodeMeta } from "@/backend/metadata/types";
+import {
+ DetailedMeta,
+ decodeTMDBId,
+ getMetaFromId,
+} from "@/backend/metadata/getmeta";
+import {
+ MWMediaType,
+ MWSeasonWithEpisodeMeta,
+} from "@/backend/metadata/types/mw";
import { IconPatch } from "@/components/buttons/IconPatch";
import { Icons } from "@/components/Icon";
import { Loading } from "@/components/layout/Loading";
@@ -181,7 +187,7 @@ export function MediaView() {
const [selected, setSelected] = useState(null);
const [exec, loading, error] = useLoading(
async (mediaParams: string, seasonId?: string) => {
- const data = decodeJWId(mediaParams);
+ const data = decodeTMDBId(mediaParams);
if (!data) return null;
return getMetaFromId(data.type, data.id, seasonId);
}
diff --git a/src/views/other/v2Migration.tsx b/src/views/other/v2Migration.tsx
index 1334ae26..d0b05e42 100644
--- a/src/views/other/v2Migration.tsx
+++ b/src/views/other/v2Migration.tsx
@@ -1,7 +1,7 @@
import pako from "pako";
import { useEffect, useState } from "react";
-import { MWMediaType } from "@/backend/metadata/types";
+import { MWMediaType } from "@/backend/metadata/types/mw";
import { conf } from "@/setup/config";
function fromBinary(str: string): Uint8Array {
diff --git a/src/views/search/SearchResultsPartial.tsx b/src/views/search/SearchResultsPartial.tsx
index 5769338b..e7cfc509 100644
--- a/src/views/search/SearchResultsPartial.tsx
+++ b/src/views/search/SearchResultsPartial.tsx
@@ -1,6 +1,6 @@
import { useEffect, useMemo, useState } from "react";
-import { MWQuery } from "@/backend/metadata/types";
+import { MWQuery } from "@/backend/metadata/types/mw";
import { useDebounce } from "@/hooks/useDebounce";
import { HomeView } from "./HomeView";
diff --git a/src/views/search/SearchResultsView.tsx b/src/views/search/SearchResultsView.tsx
index 331d4f2d..f6507ef1 100644
--- a/src/views/search/SearchResultsView.tsx
+++ b/src/views/search/SearchResultsView.tsx
@@ -2,7 +2,7 @@ import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { searchForMedia } from "@/backend/metadata/search";
-import { MWMediaMeta, MWQuery } from "@/backend/metadata/types";
+import { MWMediaMeta, MWQuery } from "@/backend/metadata/types/mw";
import { IconPatch } from "@/components/buttons/IconPatch";
import { Icons } from "@/components/Icon";
import { SectionHeading } from "@/components/layout/SectionHeading";