new config system

This commit is contained in:
Jelle van Snik 2022-12-27 16:44:36 +01:00
parent 2e8025a241
commit 388827b56f
20 changed files with 168 additions and 69 deletions

View file

@ -1,26 +1,32 @@
const a11yOff = Object.keys(require('eslint-plugin-jsx-a11y').rules)
.reduce((acc, rule) => { acc[`jsx-a11y/${rule}`] = 'off'; return acc }, {})
const a11yOff = Object.keys(require("eslint-plugin-jsx-a11y").rules).reduce(
(acc, rule) => {
acc[`jsx-a11y/${rule}`] = "off";
return acc;
},
{}
);
module.exports = {
extends: [
"airbnb",
"airbnb/hooks",
"plugin:@typescript-eslint/recommended",
"prettier",
"prettier"
],
settings: {
"import/resolver": {
typescript: {},
},
typescript: {}
}
},
ignorePatterns: ["public/*", "/*.js", "/*.ts"],
parser: "@typescript-eslint/parser",
parserOptions: {
project: "./tsconfig.json",
tsconfigRootDir: "./",
tsconfigRootDir: "./"
},
plugins: ["@typescript-eslint", "import"],
env: {
browser: true,
browser: true
},
rules: {
"react/jsx-uses-react": "off",
@ -43,16 +49,16 @@ module.exports = {
"no-await-in-loop": "off",
"react/jsx-filename-extension": [
"error",
{ extensions: [".js", ".tsx", ".jsx"] },
{ extensions: [".js", ".tsx", ".jsx"] }
],
"import/extensions": [
"error",
"ignorePackages",
{
ts: "never",
tsx: "never",
},
tsx: "never"
}
],
...a11yOff
},
}
};

5
.gitignore vendored
View file

@ -22,4 +22,7 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*
package-lock.json
package-lock.json
# config
.env

View file

@ -45,7 +45,7 @@ yarn start
To build production files, simply run `yarn build`.
You'll need to deploy a cloudflare service worker as well. Check the [selfhosting guide](https://github.com/movie-web/movie-web/blob/dev/SELFHOSTING.md) on how to run the service worker. Afterwards update the proxy URL constant in `/src/mw-constants.ts` with your service worker.
You'll need to deploy a cloudflare service worker as well. Check the [selfhosting guide](https://github.com/movie-web/movie-web/blob/dev/SELFHOSTING.md) on how to run the service worker. Afterwards you can make a `.env` file and put in the URL. (see `example.env` for an example)
<h2>Contributing - <a href="https://github.com/JamesHawkinss/movie-web/issues"><img alt="GitHub issues" src="https://img.shields.io/github/issues/JamesHawkinss/movie-web?style=flat-square"></a>
<a href="https://github.com/JamesHawkinss/movie-web/pulls"><img alt="GitHub pull requests" src="https://img.shields.io/github/issues-pr/JamesHawkinss/movie-web?style=flat-square"></a></h2>

View file

@ -28,6 +28,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)
2. Extract the zip file so you can edit the files.
3. Open `config.js` in notepad, VScode or similar.
4. Put your cloudflare proxy URL inbetween the double qoutes of `VITE_CORS_PROXY_URL: "",`.
> Whoops, the rest of this guide hasn't been written yet.
Check back soon.
Example (THIS IS MINE, IT WONT WORK FOR YOU): `VITE_CORS_PROXY_URL: "https://test-proxy.test.workers.dev/",`
5. Save the file
Your client has been prepared, you can now host it on any webhost.
It doesn't require php, its just a standard static page.

6
example.env Normal file
View file

@ -0,0 +1,6 @@
# make sure the cors proxy url does NOT have a slash at the end
VITE_CORS_PROXY_URL=...
# the keys below are optional - defaults are provided
VITE_TMDB_API_KEY=...
VITE_OMDB_API_KEY=...

View file

@ -39,6 +39,7 @@
rel="stylesheet"
/>
<script src="config.js"></script>
<title>movie-web</title>
</head>
<body>

5
public/config.js Normal file
View file

@ -0,0 +1,5 @@
window.__CONFIG__ = {
VITE_CORS_PROXY_URL: "",
VITE_TMDB_API_KEY: "b030404650f279792a8d3287232358e3",
VITE_OMDB_API_KEY: "aa0937c0"
};

View file

@ -3,7 +3,7 @@ import { IconPatch } from "@/components/buttons/IconPatch";
import { Icons } from "@/components/Icon";
import { Link } from "@/components/text/Link";
import { Title } from "@/components/text/Title";
import { DISCORD_LINK, GITHUB_LINK } from "@/mw_constants";
import { conf } from "@/config";
interface ErrorBoundaryState {
hasError: boolean;
@ -58,11 +58,11 @@ export class ErrorBoundary extends Component<
<p className="my-6 max-w-lg">
The app encountered an error and wasn&apos;t able to recover, please
report it to the{" "}
<Link url={DISCORD_LINK} newTab>
<Link url={conf().DISCORD_LINK} newTab>
Discord server
</Link>{" "}
or on{" "}
<Link url={GITHUB_LINK} newTab>
<Link url={conf().GITHUB_LINK} newTab>
GitHub
</Link>
.

View file

@ -2,7 +2,7 @@ import { ReactNode } from "react";
import { Link } from "react-router-dom";
import { IconPatch } from "@/components/buttons/IconPatch";
import { Icons } from "@/components/Icon";
import { DISCORD_LINK, GITHUB_LINK } from "@/mw_constants";
import { conf } from "@/config";
import { BrandPill } from "./BrandPill";
export interface NavigationProps {
@ -26,7 +26,7 @@ export function Navigation(props: NavigationProps) {
} flex-row gap-4`}
>
<a
href={DISCORD_LINK}
href={conf().DISCORD_LINK}
target="_blank"
rel="noreferrer"
className="text-2xl text-white"
@ -34,7 +34,7 @@ export function Navigation(props: NavigationProps) {
<IconPatch icon={Icons.DISCORD} clickable />
</a>
<a
href={GITHUB_LINK}
href={conf().GITHUB_LINK}
target="_blank"
rel="noreferrer"
className="text-2xl text-white"

50
src/config.ts Normal file
View file

@ -0,0 +1,50 @@
import { APP_VERSION, GITHUB_LINK, DISCORD_LINK } from "@/constants";
export interface Config {
APP_VERSION: string;
GITHUB_LINK: string;
DISCORD_LINK: string;
OMDB_API_KEY: string;
TMDB_API_KEY: string;
CORS_PROXY_URL: string;
}
const env: Record<keyof Config, undefined | string> = {
OMDB_API_KEY: import.meta.env.VITE_OMDB_API_KEY,
TMDB_API_KEY: import.meta.env.VITE_TMDB_API_KEY,
APP_VERSION: undefined,
GITHUB_LINK: undefined,
DISCORD_LINK: undefined,
CORS_PROXY_URL: import.meta.env.VITE_CORS_PROXY_URL,
};
const alerts = [] as string[];
// loads from different locations, in order: environment (VITE_{KEY}), window (public/config.js)
function getKey(key: keyof Config): string {
let windowValue = (window as any)?.__CONFIG__?.[`VITE_${key}`];
if (windowValue !== undefined && windowValue.length === 0)
windowValue = undefined;
const value = env[key] ?? windowValue ?? undefined;
if (value === undefined) {
if (!alerts.includes(key)) {
// eslint-disable-next-line no-alert
window.alert(`Misconfigured instance, missing key: ${key}`);
alerts.push(key);
}
return "";
}
return value;
}
export function conf(): Config {
return {
APP_VERSION,
GITHUB_LINK,
DISCORD_LINK,
OMDB_API_KEY: getKey("OMDB_API_KEY"),
TMDB_API_KEY: getKey("TMDB_API_KEY"),
CORS_PROXY_URL: `${getKey("CORS_PROXY_URL")}/?destination=`,
};
}

3
src/constants.ts Normal file
View file

@ -0,0 +1,3 @@
export const DISCORD_LINK = "https://discord.gg/Jhqt4Xzpfb";
export const GITHUB_LINK = "https://github.com/JamesHawkinss/movie-web";
export const APP_VERSION = "2.1.0";

View file

@ -1,7 +0,0 @@
export const CORS_PROXY_URL =
"https://cors.squeezebox.dev/?destination=";
export const TMDB_API_KEY = "b030404650f279792a8d3287232358e3";
export const OMDB_API_KEY = "aa0937c0";
export const DISCORD_LINK = "https://discord.gg/Jhqt4Xzpfb";
export const GITHUB_LINK = "https://github.com/JamesHawkinss/movie-web";
export const APP_VERSION = "2.1.0";

View file

@ -7,7 +7,7 @@ import {
MWProviderMediaResult,
} from "@/providers/types";
import { CORS_PROXY_URL } from "@/mw_constants";
import { conf } from "@/config";
export const flixhqProvider: MWMediaProvider = {
id: "flixhq",
@ -19,7 +19,9 @@ export const flixhqProvider: MWMediaProvider = {
media: MWPortableMedia
): Promise<MWProviderMediaResult> {
const searchRes = await fetch(
`${CORS_PROXY_URL}https://api.consumet.org/movies/flixhq/info?id=${encodeURIComponent(
`${
conf().CORS_PROXY_URL
}https://api.consumet.org/movies/flixhq/info?id=${encodeURIComponent(
media.mediaId
)}`
).then((d) => d.json());
@ -33,7 +35,9 @@ export const flixhqProvider: MWMediaProvider = {
async searchForMedia(query: MWQuery): Promise<MWProviderMediaResult[]> {
const searchRes = await fetch(
`${CORS_PROXY_URL}https://api.consumet.org/movies/flixhq/${encodeURIComponent(
`${
conf().CORS_PROXY_URL
}https://api.consumet.org/movies/flixhq/${encodeURIComponent(
query.searchQuery
)}`
).then((d) => d.json());
@ -52,7 +56,9 @@ export const flixhqProvider: MWMediaProvider = {
async getStream(media: MWPortableMedia): Promise<MWMediaStream> {
const searchRes = await fetch(
`${CORS_PROXY_URL}https://api.consumet.org/movies/flixhq/info?id=${encodeURIComponent(
`${
conf().CORS_PROXY_URL
}https://api.consumet.org/movies/flixhq/info?id=${encodeURIComponent(
media.mediaId
)}`
).then((d) => d.json());
@ -63,7 +69,9 @@ export const flixhqProvider: MWMediaProvider = {
});
const watchRes = await fetch(
`${CORS_PROXY_URL}https://api.consumet.org/movies/flixhq/watch?${encodeURIComponent(
`${
conf().CORS_PROXY_URL
}https://api.consumet.org/movies/flixhq/watch?${encodeURIComponent(
params.toString()
)}`
).then((d) => d.json());

View file

@ -9,7 +9,7 @@ import {
MWProviderMediaResult,
} from "@/providers/types";
import { CORS_PROXY_URL } from "@/mw_constants";
import { conf } from "@/config";
const format = {
stringify: (cipher: any) => {
@ -47,7 +47,9 @@ export const gDrivePlayerScraper: MWMediaProvider = {
media: MWPortableMedia
): Promise<MWProviderMediaResult> {
const res = await fetch(
`${CORS_PROXY_URL}https://api.gdriveplayer.us/v1/imdb/${media.mediaId}`
`${conf().CORS_PROXY_URL}https://api.gdriveplayer.us/v1/imdb/${
media.mediaId
}`
).then((d) => d.json());
return {
@ -59,7 +61,9 @@ export const gDrivePlayerScraper: MWMediaProvider = {
async searchForMedia(query: MWQuery): Promise<MWProviderMediaResult[]> {
const searchRes = await fetch(
`${CORS_PROXY_URL}https://api.gdriveplayer.us/v1/movie/search?title=${query.searchQuery}`
`${
conf().CORS_PROXY_URL
}https://api.gdriveplayer.us/v1/movie/search?title=${query.searchQuery}`
).then((d) => d.json());
const results: MWProviderMediaResult[] = (searchRes || []).map(
@ -75,7 +79,9 @@ export const gDrivePlayerScraper: MWMediaProvider = {
async getStream(media: MWPortableMedia): Promise<MWMediaStream> {
const streamRes = await fetch(
`${CORS_PROXY_URL}https://database.gdriveplayer.us/player.php?imdb=${media.mediaId}`
`${
conf().CORS_PROXY_URL
}https://database.gdriveplayer.us/player.php?imdb=${media.mediaId}`
).then((d) => d.text());
const page = new DOMParser().parseFromString(streamRes, "text/html");

View file

@ -9,7 +9,7 @@ import {
MWProviderMediaResult,
} from "@/providers/types";
import { CORS_PROXY_URL, OMDB_API_KEY } from "@/mw_constants";
import { conf } from "@/config";
export const gomostreamScraper: MWMediaProvider = {
id: "gomostream",
@ -21,13 +21,13 @@ export const gomostreamScraper: MWMediaProvider = {
media: MWPortableMedia
): Promise<MWProviderMediaResult> {
const params = new URLSearchParams({
apikey: OMDB_API_KEY,
apikey: conf().OMDB_API_KEY,
i: media.mediaId,
type: media.mediaType,
});
const res = await fetch(
`${CORS_PROXY_URL}http://www.omdbapi.com/?${encodeURIComponent(
`${conf().CORS_PROXY_URL}http://www.omdbapi.com/?${encodeURIComponent(
params.toString()
)}`
).then((d) => d.json());
@ -43,12 +43,12 @@ export const gomostreamScraper: MWMediaProvider = {
const term = query.searchQuery.toLowerCase();
const params = new URLSearchParams({
apikey: OMDB_API_KEY,
apikey: conf().OMDB_API_KEY,
s: term,
type: query.type,
});
const searchRes = await fetch(
`${CORS_PROXY_URL}http://www.omdbapi.com/?${encodeURIComponent(
`${conf().CORS_PROXY_URL}http://www.omdbapi.com/?${encodeURIComponent(
params.toString()
)}`
).then((d) => d.json());
@ -69,7 +69,7 @@ export const gomostreamScraper: MWMediaProvider = {
const type =
media.mediaType === MWMediaType.SERIES ? "show" : media.mediaType;
const res1 = await fetch(
`${CORS_PROXY_URL}https://gomo.to/${type}/${media.mediaId}`
`${conf().CORS_PROXY_URL}https://gomo.to/${type}/${media.mediaId}`
).then((d) => d.text());
if (res1 === "Movie not available." || res1 === "Episode not available.")
throw new Error(res1);
@ -82,7 +82,7 @@ export const gomostreamScraper: MWMediaProvider = {
fd.append("_token", _token);
const src = await fetch(
`${CORS_PROXY_URL}https://gomo.to/decoding_v3.php`,
`${conf().CORS_PROXY_URL}https://gomo.to/decoding_v3.php`,
{
method: "POST",
body: fd,
@ -95,7 +95,7 @@ export const gomostreamScraper: MWMediaProvider = {
// maybe try all embeds in the future
const embedUrl = embeds[1];
const res2 = await fetch(`${CORS_PROXY_URL}${embedUrl}`).then((d) =>
const res2 = await fetch(`${conf().CORS_PROXY_URL}${embedUrl}`).then((d) =>
d.text()
);

View file

@ -4,7 +4,7 @@
import { customAlphabet } from "nanoid";
import toWebVTT from "srt-webvtt";
import CryptoJS from "crypto-js";
import { CORS_PROXY_URL, TMDB_API_KEY } from "@/mw_constants";
import { conf } from "@/config";
import {
MWMediaProvider,
MWMediaType,
@ -85,7 +85,7 @@ const get = (data: object, altApi = false) => {
formatted.append("medium", "Website");
const requestUrl = altApi ? apiUrls[1] : apiUrls[0];
return fetch(`${CORS_PROXY_URL}${requestUrl}`, {
return fetch(`${conf().CORS_PROXY_URL}${requestUrl}`, {
method: "POST",
headers: {
Platform: "android",
@ -200,7 +200,7 @@ export const superStreamScraper: MWMediaProvider = {
const mappedCaptions = await Promise.all(
subtitleRes.list.map(async (subtitle: any) => {
const captionBlob = await fetch(
`${CORS_PROXY_URL}${subtitle.subtitles[0].file_path}`
`${conf().CORS_PROXY_URL}${subtitle.subtitles[0].file_path}`
).then((captionRes) => captionRes.blob()); // cross-origin bypass
const captionUrl = await toWebVTT(captionBlob); // convert to vtt so it's playable
return {
@ -253,7 +253,7 @@ export const superStreamScraper: MWMediaProvider = {
const mappedCaptions = await Promise.all(
subtitleRes.list.map(async (subtitle: any) => {
const captionBlob = await fetch(
`${CORS_PROXY_URL}${subtitle.subtitles[0].file_path}`
`${conf().CORS_PROXY_URL}${subtitle.subtitles[0].file_path}`
).then((captionRes) => captionRes.blob()); // cross-origin bypass
const captionUrl = await toWebVTT(captionBlob); // convert to vtt so it's playable
return {
@ -277,11 +277,15 @@ export const superStreamScraper: MWMediaProvider = {
const detailRes = (await get(apiQuery, true).then((r) => r.json())).data;
const firstSearchResult = (
await fetch(
`https://api.themoviedb.org/3/search/tv?api_key=${TMDB_API_KEY}&language=en-US&page=1&query=${detailRes.title}&include_adult=false`
`https://api.themoviedb.org/3/search/tv?api_key=${
conf().TMDB_API_KEY
}&language=en-US&page=1&query=${detailRes.title}&include_adult=false`
).then((r) => r.json())
).results[0];
const showDetails = await fetch(
`https://api.themoviedb.org/3/tv/${firstSearchResult.id}?api_key=${TMDB_API_KEY}`
`https://api.themoviedb.org/3/tv/${firstSearchResult.id}?api_key=${
conf().TMDB_API_KEY
}`
).then((r) => r.json());
return {

View file

@ -15,7 +15,7 @@ import {
} from "@/providers/list/theflix/search";
import { getDataFromPortableSearch } from "@/providers/list/theflix/portableToMedia";
import { CORS_PROXY_URL } from "@/mw_constants";
import { conf } from "@/config";
export const theFlixScraper: MWMediaProvider = {
id: "theflix",
@ -51,9 +51,13 @@ export const theFlixScraper: MWMediaProvider = {
let url = "";
if (media.mediaType === MWMediaType.MOVIE) {
url = `${CORS_PROXY_URL}https://theflix.to/movie/${media.mediaId}?movieInfo=${media.mediaId}`;
url = `${conf().CORS_PROXY_URL}https://theflix.to/movie/${
media.mediaId
}?movieInfo=${media.mediaId}`;
} else if (media.mediaType === MWMediaType.SERIES) {
url = `${CORS_PROXY_URL}https://theflix.to/tv-show/${media.mediaId}/season-${media.seasonId}/episode-${media.episodeId}`;
url = `${conf().CORS_PROXY_URL}https://theflix.to/tv-show/${
media.mediaId
}/season-${media.seasonId}/episode-${media.episodeId}`;
}
const res = await fetch(url).then((d) => d.text());
@ -76,7 +80,9 @@ export const theFlixScraper: MWMediaProvider = {
async getSeasonDataFromMedia(
media: MWPortableMedia
): Promise<MWMediaSeasons> {
const url = `${CORS_PROXY_URL}https://theflix.to/tv-show/${media.mediaId}/season-${media.seasonId}/episode-${media.episodeId}`;
const url = `${conf().CORS_PROXY_URL}https://theflix.to/tv-show/${
media.mediaId
}/season-${media.seasonId}/episode-${media.episodeId}`;
const res = await fetch(url).then((d) => d.text());
const node: Element = Array.from(

View file

@ -1,4 +1,4 @@
import { CORS_PROXY_URL } from "@/mw_constants";
import { conf } from "@/config";
import { MWMediaType, MWPortableMedia } from "@/providers/types";
const getTheFlixUrl = (media: MWPortableMedia, params?: URLSearchParams) => {
@ -18,9 +18,9 @@ export async function getDataFromPortableSearch(
const params = new URLSearchParams();
params.append("movieInfo", media.mediaId);
const res = await fetch(CORS_PROXY_URL + getTheFlixUrl(media, params)).then(
(d) => d.text()
);
const res = await fetch(
conf().CORS_PROXY_URL + getTheFlixUrl(media, params)
).then((d) => d.text());
const node: Element = Array.from(
new DOMParser()

View file

@ -1,4 +1,4 @@
import { CORS_PROXY_URL } from "@/mw_constants";
import { conf } from "@/config";
import { MWMediaType, MWProviderMediaResult, MWQuery } from "@/providers";
const getTheFlixUrl = (type: "tv-shows" | "movies", params: URLSearchParams) =>
@ -8,7 +8,7 @@ export function searchTheFlix(query: MWQuery): Promise<string> {
const params = new URLSearchParams();
params.append("search", query.searchQuery);
return fetch(
CORS_PROXY_URL +
conf().CORS_PROXY_URL +
getTheFlixUrl(
query.type === MWMediaType.MOVIE ? "movies" : "tv-shows",
params

View file

@ -8,7 +8,7 @@ import {
MWMediaCaption,
} from "@/providers/types";
import { CORS_PROXY_URL } from "@/mw_constants";
import { conf } from "@/config";
export const xemovieScraper: MWMediaProvider = {
id: "xemovie",
@ -20,7 +20,7 @@ export const xemovieScraper: MWMediaProvider = {
media: MWPortableMedia
): Promise<MWProviderMediaResult> {
const res = await fetch(
`${CORS_PROXY_URL}https://xemovie.co/movies/${media.mediaId}/watch`
`${conf().CORS_PROXY_URL}https://xemovie.co/movies/${media.mediaId}/watch`
).then((d) => d.text());
const DOM = new DOMParser().parseFromString(res, "text/html");
@ -42,9 +42,9 @@ export const xemovieScraper: MWMediaProvider = {
async searchForMedia(query: MWQuery): Promise<MWProviderMediaResult[]> {
const term = query.searchQuery.toLowerCase();
const searchUrl = `${CORS_PROXY_URL}https://xemovie.co/search?q=${encodeURIComponent(
term
)}`;
const searchUrl = `${
conf().CORS_PROXY_URL
}https://xemovie.co/search?q=${encodeURIComponent(term)}`;
const searchRes = await fetch(searchUrl).then((d) => d.text());
const parser = new DOMParser();
@ -81,7 +81,9 @@ export const xemovieScraper: MWMediaProvider = {
if (media.mediaType !== MWMediaType.MOVIE)
throw new Error("Incorrect type");
const url = `${CORS_PROXY_URL}https://xemovie.co/movies/${media.mediaId}/watch`;
const url = `${conf().CORS_PROXY_URL}https://xemovie.co/movies/${
media.mediaId
}/watch`;
let streamUrl = "";
const subtitles: MWMediaCaption[] = [];
@ -100,7 +102,8 @@ export const xemovieScraper: MWMediaProvider = {
const data = JSON.parse(
JSON.stringify(
eval(
`(${script.textContent.replace("const data = ", "").split("};")[0]
`(${
script.textContent.replace("const data = ", "").split("};")[0]
}})`
)
)
@ -112,7 +115,7 @@ export const xemovieScraper: MWMediaProvider = {
subtitleTrack,
] of data.playlist[0].tracks.entries()) {
const subtitleBlob = URL.createObjectURL(
await fetch(`${CORS_PROXY_URL}${subtitleTrack.file}`).then(
await fetch(`${conf().CORS_PROXY_URL}${subtitleTrack.file}`).then(
(captionRes) => captionRes.blob()
)
); // do this so no need for CORS errors