mirror of
https://github.com/movie-web/movie-web.git
synced 2024-12-27 23:41:49 +01:00
Merge branch 'dev' into v4
This commit is contained in:
commit
18f0e55bb3
@ -29,10 +29,11 @@ Your proxy is now hosted on cloudflare. Note the url of your worker. you will ne
|
|||||||
1. Download the file `movie-web.zip` from the latest release: [https://github.com/movie-web/movie-web/releases/latest](https://github.com/movie-web/movie-web/releases/latest)
|
1. Download the file `movie-web.zip` from the latest release: [https://github.com/movie-web/movie-web/releases/latest](https://github.com/movie-web/movie-web/releases/latest)
|
||||||
2. Extract the zip file so you can edit the files.
|
2. Extract the zip file so you can edit the files.
|
||||||
3. Open `config.js` in notepad, VScode or similar.
|
3. Open `config.js` in notepad, VScode or similar.
|
||||||
4. Put your cloudflare proxy URL inbetween the double qoutes of `VITE_CORS_PROXY_URL: "",`. Make sure to not have a slash at the end of your URL.
|
4. Put your cloudflare proxy URL inbetween the double qoutes of `VITE_CORS_PROXY_URL: ""`. Make sure to not have a slash at the end of your URL.
|
||||||
|
|
||||||
Example (THIS IS MINE, IT WONT WORK FOR YOU): `VITE_CORS_PROXY_URL: "https://test-proxy.test.workers.dev",`
|
Example (THIS IS MINE, IT WONT WORK FOR YOU): `VITE_CORS_PROXY_URL: "https://test-proxy.test.workers.dev"`
|
||||||
5. Save the file
|
5. Put your TMDB read access token inside the quotes of `VITE_TMDB_READ_API_KEY: ""`. You can generate it for free at [https://www.themoviedb.org/settings/api](https://www.themoviedb.org/settings/api).
|
||||||
|
6. Save the file
|
||||||
|
|
||||||
Your client has been prepared, you can now host it on any webhost.
|
Your client has been prepared, you can now host it on any webhost.
|
||||||
It doesn't require php, its just a standard static page.
|
It doesn't require php, its just a standard static page.
|
||||||
|
@ -1,6 +1,3 @@
|
|||||||
# make sure the cors proxy url does NOT have a slash at the end
|
# make sure the cors proxy url does NOT have a slash at the end
|
||||||
VITE_CORS_PROXY_URL=...
|
VITE_CORS_PROXY_URL=...
|
||||||
|
VITE_TMDB_READ_API_KEY=...
|
||||||
# the keys below are optional - defaults are provided
|
|
||||||
VITE_TMDB_API_KEY=...
|
|
||||||
VITE_OMDB_API_KEY=...
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
window.__CONFIG__ = {
|
window.__CONFIG__ = {
|
||||||
// url must NOT end with a slash
|
// url must NOT end with a slash
|
||||||
VITE_CORS_PROXY_URL: "",
|
VITE_CORS_PROXY_URL: "",
|
||||||
VITE_TMDB_API_KEY: "b030404650f279792a8d3287232358e3",
|
VITE_TMDB_READ_API_KEY: ""
|
||||||
VITE_OMDB_API_KEY: "aa0937c0",
|
|
||||||
};
|
};
|
||||||
|
@ -73,7 +73,7 @@ export function formatTMDBMetaResult(
|
|||||||
season_number: v.season_number,
|
season_number: v.season_number,
|
||||||
title: v.name,
|
title: v.name,
|
||||||
})),
|
})),
|
||||||
poster: (details as TMDBMovieData).poster_path ?? undefined,
|
poster: getMediaPoster(show.poster_path) ?? undefined,
|
||||||
original_release_year: new Date(show.first_air_date).getFullYear(),
|
original_release_year: new Date(show.first_air_date).getFullYear(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -99,7 +99,7 @@ const baseURL = "https://api.themoviedb.org/3";
|
|||||||
|
|
||||||
const headers = {
|
const headers = {
|
||||||
accept: "application/json",
|
accept: "application/json",
|
||||||
Authorization: `Bearer ${conf().TMDB_API_KEY}`,
|
Authorization: `Bearer ${conf().TMDB_READ_API_KEY}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
async function get<T>(url: string, params?: object): Promise<T> {
|
async function get<T>(url: string, params?: object): Promise<T> {
|
||||||
@ -143,21 +143,24 @@ export async function searchMedia(
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getMediaDetails(id: string, type: TMDBContentTypes) {
|
// Conditional type which for inferring the return type based on the content type
|
||||||
let data;
|
type MediaDetailReturn<T extends TMDBContentTypes> = T extends "movie"
|
||||||
|
? TMDBMovieData
|
||||||
|
: T extends "show"
|
||||||
|
? TMDBShowData
|
||||||
|
: never;
|
||||||
|
|
||||||
switch (type) {
|
export function getMediaDetails<
|
||||||
case "movie":
|
T extends TMDBContentTypes,
|
||||||
data = await get<TMDBMovieData>(`/movie/${id}`);
|
TReturn = MediaDetailReturn<T>
|
||||||
break;
|
>(id: string, type: T): Promise<TReturn> {
|
||||||
case "show":
|
if (type === "movie") {
|
||||||
data = await get<TMDBShowData>(`/tv/${id}`);
|
return get<TReturn>(`/movie/${id}`);
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Error("Invalid media type");
|
|
||||||
}
|
}
|
||||||
|
if (type === "show") {
|
||||||
return data;
|
return get<TReturn>(`/tv/${id}`);
|
||||||
|
}
|
||||||
|
throw new Error("Invalid media type");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMediaPoster(posterPath: string | null): string | undefined {
|
export function getMediaPoster(posterPath: string | null): string | undefined {
|
||||||
|
@ -8,7 +8,7 @@ const gomoviesBase = "https://gomovies.sx";
|
|||||||
registerProvider({
|
registerProvider({
|
||||||
id: "gomovies",
|
id: "gomovies",
|
||||||
displayName: "GOmovies",
|
displayName: "GOmovies",
|
||||||
rank: 300,
|
rank: 200,
|
||||||
type: [MWMediaType.MOVIE, MWMediaType.SERIES],
|
type: [MWMediaType.MOVIE, MWMediaType.SERIES],
|
||||||
|
|
||||||
async scrape({ media, episode }) {
|
async scrape({ media, episode }) {
|
||||||
|
@ -142,7 +142,7 @@ const convertSubtitles = (subtitleGroup: any): MWCaption | null => {
|
|||||||
registerProvider({
|
registerProvider({
|
||||||
id: "superstream",
|
id: "superstream",
|
||||||
displayName: "Superstream",
|
displayName: "Superstream",
|
||||||
rank: 200,
|
rank: 300,
|
||||||
type: [MWMediaType.MOVIE, MWMediaType.SERIES],
|
type: [MWMediaType.MOVIE, MWMediaType.SERIES],
|
||||||
|
|
||||||
async scrape({ media, episode, progress }) {
|
async scrape({ media, episode, progress }) {
|
||||||
|
@ -7,7 +7,7 @@ import { registerSW } from "virtual:pwa-register";
|
|||||||
|
|
||||||
import { ErrorBoundary } from "@/components/layout/ErrorBoundary";
|
import { ErrorBoundary } from "@/components/layout/ErrorBoundary";
|
||||||
import App from "@/setup/App";
|
import App from "@/setup/App";
|
||||||
import { conf } from "@/setup/config";
|
import { assertConfig, conf } from "@/setup/config";
|
||||||
import i18n from "@/setup/i18n";
|
import i18n from "@/setup/i18n";
|
||||||
|
|
||||||
import "@/setup/ga";
|
import "@/setup/ga";
|
||||||
@ -30,6 +30,7 @@ registerSW({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const LazyLoadedApp = React.lazy(async () => {
|
const LazyLoadedApp = React.lazy(async () => {
|
||||||
|
await assertConfig();
|
||||||
await initializeStores();
|
await initializeStores();
|
||||||
i18n.changeLanguage(SettingsStore.get().language ?? "en");
|
i18n.changeLanguage(SettingsStore.get().language ?? "en");
|
||||||
return {
|
return {
|
||||||
|
@ -4,8 +4,7 @@ interface Config {
|
|||||||
APP_VERSION: string;
|
APP_VERSION: string;
|
||||||
GITHUB_LINK: string;
|
GITHUB_LINK: string;
|
||||||
DISCORD_LINK: string;
|
DISCORD_LINK: string;
|
||||||
OMDB_API_KEY: string;
|
TMDB_READ_API_KEY: string;
|
||||||
TMDB_API_KEY: string;
|
|
||||||
CORS_PROXY_URL: string;
|
CORS_PROXY_URL: string;
|
||||||
NORMAL_ROUTER: boolean;
|
NORMAL_ROUTER: boolean;
|
||||||
}
|
}
|
||||||
@ -14,15 +13,13 @@ export interface RuntimeConfig {
|
|||||||
APP_VERSION: string;
|
APP_VERSION: string;
|
||||||
GITHUB_LINK: string;
|
GITHUB_LINK: string;
|
||||||
DISCORD_LINK: string;
|
DISCORD_LINK: string;
|
||||||
OMDB_API_KEY: string;
|
TMDB_READ_API_KEY: string;
|
||||||
TMDB_API_KEY: string;
|
|
||||||
NORMAL_ROUTER: boolean;
|
NORMAL_ROUTER: boolean;
|
||||||
PROXY_URLS: string[];
|
PROXY_URLS: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const env: Record<keyof Config, undefined | string> = {
|
const env: Record<keyof Config, undefined | string> = {
|
||||||
OMDB_API_KEY: import.meta.env.VITE_OMDB_API_KEY,
|
TMDB_READ_API_KEY: import.meta.env.VITE_TMDB_READ_API_KEY,
|
||||||
TMDB_API_KEY: import.meta.env.VITE_TMDB_API_KEY,
|
|
||||||
APP_VERSION: undefined,
|
APP_VERSION: undefined,
|
||||||
GITHUB_LINK: undefined,
|
GITHUB_LINK: undefined,
|
||||||
DISCORD_LINK: undefined,
|
DISCORD_LINK: undefined,
|
||||||
@ -30,25 +27,28 @@ const env: Record<keyof Config, undefined | string> = {
|
|||||||
NORMAL_ROUTER: import.meta.env.VITE_NORMAL_ROUTER,
|
NORMAL_ROUTER: import.meta.env.VITE_NORMAL_ROUTER,
|
||||||
};
|
};
|
||||||
|
|
||||||
const alerts = [] as string[];
|
|
||||||
|
|
||||||
// loads from different locations, in order: environment (VITE_{KEY}), window (public/config.js)
|
// loads from different locations, in order: environment (VITE_{KEY}), window (public/config.js)
|
||||||
function getKey(key: keyof Config, defaultString?: string): string {
|
function getKeyValue(key: keyof Config): string | undefined {
|
||||||
let windowValue = (window as any)?.__CONFIG__?.[`VITE_${key}`];
|
let windowValue = (window as any)?.__CONFIG__?.[`VITE_${key}`];
|
||||||
if (windowValue !== undefined && windowValue.length === 0)
|
if (windowValue !== undefined && windowValue.length === 0)
|
||||||
windowValue = undefined;
|
windowValue = undefined;
|
||||||
const value = env[key] ?? windowValue ?? undefined;
|
return env[key] ?? windowValue ?? undefined;
|
||||||
if (value === undefined) {
|
}
|
||||||
if (defaultString) return defaultString;
|
|
||||||
if (!alerts.includes(key)) {
|
|
||||||
// eslint-disable-next-line no-alert
|
|
||||||
window.alert(`Misconfigured instance, missing key: ${key}`);
|
|
||||||
alerts.push(key);
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
function getKey(key: keyof Config, defaultString?: string): string {
|
||||||
|
return getKeyValue(key) ?? defaultString ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function assertConfig() {
|
||||||
|
const keys: Array<keyof Config> = ["TMDB_READ_API_KEY", "CORS_PROXY_URL"];
|
||||||
|
const values = keys.map((key) => {
|
||||||
|
const val = getKeyValue(key);
|
||||||
|
if (val) return val;
|
||||||
|
// eslint-disable-next-line no-alert
|
||||||
|
window.alert(`Misconfigured instance, missing key: ${key}`);
|
||||||
|
return val;
|
||||||
|
});
|
||||||
|
if (values.includes(undefined)) throw new Error("Misconfigured instance");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function conf(): RuntimeConfig {
|
export function conf(): RuntimeConfig {
|
||||||
@ -56,8 +56,7 @@ export function conf(): RuntimeConfig {
|
|||||||
APP_VERSION,
|
APP_VERSION,
|
||||||
GITHUB_LINK,
|
GITHUB_LINK,
|
||||||
DISCORD_LINK,
|
DISCORD_LINK,
|
||||||
OMDB_API_KEY: getKey("OMDB_API_KEY"),
|
TMDB_READ_API_KEY: getKey("TMDB_READ_API_KEY"),
|
||||||
TMDB_API_KEY: getKey("TMDB_API_KEY"),
|
|
||||||
PROXY_URLS: getKey("CORS_PROXY_URL")
|
PROXY_URLS: getKey("CORS_PROXY_URL")
|
||||||
.split(",")
|
.split(",")
|
||||||
.map((v) => v.trim()),
|
.map((v) => v.trim()),
|
||||||
|
@ -14,7 +14,7 @@ export const BookmarkStore = createVersionedStore<BookmarkStoreData>()
|
|||||||
})
|
})
|
||||||
.addVersion({
|
.addVersion({
|
||||||
version: 1,
|
version: 1,
|
||||||
migrate(old: OldBookmarks) {
|
migrate(old: BookmarkStoreData) {
|
||||||
return migrateV2Bookmarks(old);
|
return migrateV2Bookmarks(old);
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -1,14 +1,20 @@
|
|||||||
import { getLegacyMetaFromId } from "@/backend/metadata/getmeta";
|
import { getLegacyMetaFromId } from "@/backend/metadata/getmeta";
|
||||||
import { getMovieFromExternalId } from "@/backend/metadata/tmdb";
|
import {
|
||||||
|
getEpisodes,
|
||||||
|
getMediaDetails,
|
||||||
|
getMovieFromExternalId,
|
||||||
|
} from "@/backend/metadata/tmdb";
|
||||||
import { MWMediaType } from "@/backend/metadata/types/mw";
|
import { MWMediaType } from "@/backend/metadata/types/mw";
|
||||||
|
import { BookmarkStoreData } from "@/state/bookmark/types";
|
||||||
|
import { isNotNull } from "@/utils/typeguard";
|
||||||
|
|
||||||
import { WatchedStoreData } from "../types";
|
import { WatchedStoreData } from "../types";
|
||||||
|
|
||||||
async function migrateId(
|
async function migrateId(
|
||||||
id: number,
|
id: string,
|
||||||
type: MWMediaType
|
type: MWMediaType
|
||||||
): Promise<string | undefined> {
|
): Promise<string | undefined> {
|
||||||
const meta = await getLegacyMetaFromId(type, id.toString());
|
const meta = await getLegacyMetaFromId(type, id);
|
||||||
|
|
||||||
if (!meta) return undefined;
|
if (!meta) return undefined;
|
||||||
const { tmdbId, imdbId } = meta;
|
const { tmdbId, imdbId } = meta;
|
||||||
@ -25,57 +31,59 @@ async function migrateId(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function migrateV2Bookmarks(old: any) {
|
export async function migrateV2Bookmarks(old: BookmarkStoreData) {
|
||||||
const oldData = old;
|
const updatedBookmarks = old.bookmarks.map(async (item) => ({
|
||||||
if (!oldData) return;
|
...item,
|
||||||
|
id: await migrateId(item.id, item.type).catch(() => undefined),
|
||||||
const updatedBookmarks = oldData.bookmarks.map(
|
}));
|
||||||
async (item: { id: number; type: MWMediaType }) => ({
|
|
||||||
...item,
|
|
||||||
id: await migrateId(item.id, item.type),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
bookmarks: (await Promise.all(updatedBookmarks)).filter((item) => item.id),
|
bookmarks: (await Promise.all(updatedBookmarks)).filter((item) => item.id),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function migrateV3Videos(old: any) {
|
export async function migrateV3Videos(
|
||||||
const oldData = old;
|
old: WatchedStoreData
|
||||||
if (!oldData) return;
|
): Promise<WatchedStoreData> {
|
||||||
|
|
||||||
const updatedItems = await Promise.all(
|
const updatedItems = await Promise.all(
|
||||||
oldData.items.map(async (item: any) => {
|
old.items.map(async (progress) => {
|
||||||
const migratedId = await migrateId(
|
try {
|
||||||
item.item.meta.id,
|
const migratedId = await migrateId(
|
||||||
item.item.meta.type
|
progress.item.meta.id,
|
||||||
);
|
progress.item.meta.type
|
||||||
|
);
|
||||||
|
|
||||||
const migratedItem = {
|
if (!migratedId) return null;
|
||||||
...item,
|
|
||||||
item: {
|
|
||||||
...item.item,
|
|
||||||
meta: {
|
|
||||||
...item.item.meta,
|
|
||||||
id: migratedId,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
const clone = structuredClone(progress);
|
||||||
...item,
|
clone.item.meta.id = migratedId;
|
||||||
item: migratedId ? migratedItem : item.item,
|
if (clone.item.series) {
|
||||||
};
|
const series = clone.item.series;
|
||||||
|
const details = await getMediaDetails(migratedId, "show");
|
||||||
|
|
||||||
|
const season = details.seasons.find(
|
||||||
|
(v) => v.season_number === series.season
|
||||||
|
);
|
||||||
|
if (!season) return null;
|
||||||
|
|
||||||
|
const episodes = await getEpisodes(migratedId, season.season_number);
|
||||||
|
const episode = episodes.find(
|
||||||
|
(v) => v.episode_number === series.episode
|
||||||
|
);
|
||||||
|
if (!episode) return null;
|
||||||
|
|
||||||
|
clone.item.series.episodeId = episode.id.toString();
|
||||||
|
clone.item.series.seasonId = season.id.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return clone;
|
||||||
|
} catch (err) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const newData: WatchedStoreData = {
|
|
||||||
items: updatedItems.map((item) => item.item),
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...oldData,
|
items: updatedItems.filter(isNotNull),
|
||||||
items: newData.items,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ export const VideoProgressStore = createVersionedStore<WatchedStoreData>()
|
|||||||
})
|
})
|
||||||
.addVersion({
|
.addVersion({
|
||||||
version: 2,
|
version: 2,
|
||||||
migrate(old: OldData) {
|
migrate(old: WatchedStoreData) {
|
||||||
return migrateV3Videos(old);
|
return migrateV3Videos(old);
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -46,8 +46,13 @@ export async function initializeStores() {
|
|||||||
let mostRecentData = data;
|
let mostRecentData = data;
|
||||||
try {
|
try {
|
||||||
for (const version of relevantVersions) {
|
for (const version of relevantVersions) {
|
||||||
if (version.migrate)
|
if (version.migrate) {
|
||||||
|
localStorage.setItem(
|
||||||
|
`BACKUP-v${version.version}-${internal.key}`,
|
||||||
|
JSON.stringify(mostRecentData)
|
||||||
|
);
|
||||||
mostRecentData = await version.migrate(mostRecentData);
|
mostRecentData = await version.migrate(mostRecentData);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`FAILED TO MIGRATE STORE ${internal.key}`, err);
|
console.error(`FAILED TO MIGRATE STORE ${internal.key}`, err);
|
||||||
|
3
src/utils/typeguard.ts
Normal file
3
src/utils/typeguard.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export function isNotNull<T>(obj: T | null): obj is T {
|
||||||
|
return obj != null;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user