mirror of
https://github.com/movie-web/movie-web.git
synced 2024-12-26 05:31:50 +01:00
turnstile integration for provider api
This commit is contained in:
parent
4847980947
commit
b5a11ef000
@ -20,7 +20,7 @@
|
|||||||
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
||||||
|
|
||||||
<script src="/config.js"></script>
|
<script src="/config.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/gh/movie-web/6C6F6C7A@8b821f445b83d51ef1b8f42c99b7346f6b47dce5/out.js"></script>
|
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit"></script>
|
||||||
|
|
||||||
<!-- prevent darkreader extension from messing with our already dark site -->
|
<!-- prevent darkreader extension from messing with our already dark site -->
|
||||||
<meta name="darkreader-lock" />
|
<meta name="darkreader-lock" />
|
||||||
@ -59,4 +59,4 @@
|
|||||||
<script type="module" src="/src/index.tsx"></script>
|
<script type="module" src="/src/index.tsx"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
@ -57,6 +57,7 @@
|
|||||||
"react-i18next": "^12.1.1",
|
"react-i18next": "^12.1.1",
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.0",
|
||||||
"react-sticky-el": "^2.1.0",
|
"react-sticky-el": "^2.1.0",
|
||||||
|
"react-turnstile": "^1.1.2",
|
||||||
"react-use": "^17.4.0",
|
"react-use": "^17.4.0",
|
||||||
"slugify": "^1.6.6",
|
"slugify": "^1.6.6",
|
||||||
"subsrt-ts": "^2.1.1",
|
"subsrt-ts": "^2.1.1",
|
||||||
|
13
pnpm-lock.yaml
generated
13
pnpm-lock.yaml
generated
@ -104,6 +104,9 @@ dependencies:
|
|||||||
react-sticky-el:
|
react-sticky-el:
|
||||||
specifier: ^2.1.0
|
specifier: ^2.1.0
|
||||||
version: 2.1.0(react-dom@17.0.2)(react@17.0.2)
|
version: 2.1.0(react-dom@17.0.2)(react@17.0.2)
|
||||||
|
react-turnstile:
|
||||||
|
specifier: ^1.1.2
|
||||||
|
version: 1.1.2(react-dom@17.0.2)(react@17.0.2)
|
||||||
react-use:
|
react-use:
|
||||||
specifier: ^17.4.0
|
specifier: ^17.4.0
|
||||||
version: 17.4.0(react-dom@17.0.2)(react@17.0.2)
|
version: 17.4.0(react-dom@17.0.2)(react@17.0.2)
|
||||||
@ -5321,6 +5324,16 @@ packages:
|
|||||||
react-dom: 17.0.2(react@17.0.2)
|
react-dom: 17.0.2(react@17.0.2)
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/react-turnstile@1.1.2(react-dom@17.0.2)(react@17.0.2):
|
||||||
|
resolution: {integrity: sha512-wfhSf4JtXlmLRkfxMryU8yEeCbh401muKoInhx+TegYwP8RprUW5XPZa8WnCNZiYpMy1i6IXAb1Ar7xj5HxJag==}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>= 17.0.0'
|
||||||
|
react-dom: '>= 17.0.0'
|
||||||
|
dependencies:
|
||||||
|
react: 17.0.2
|
||||||
|
react-dom: 17.0.2(react@17.0.2)
|
||||||
|
dev: false
|
||||||
|
|
||||||
/react-universal-interface@0.6.2(react@17.0.2)(tslib@2.6.2):
|
/react-universal-interface@0.6.2(react@17.0.2)(tslib@2.6.2):
|
||||||
resolution: {integrity: sha512-dg8yXdcQmvgR13RIlZbTRQOoUrDciFVoSBZILwjE2LFISxZZ8loVJKAkuzswl5js8BHda79bIb2b84ehU8IjXw==}
|
resolution: {integrity: sha512-dg8yXdcQmvgR13RIlZbTRQOoUrDciFVoSBZILwjE2LFISxZZ8loVJKAkuzswl5js8BHda79bIb2b84ehU8IjXw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import { MetaOutput, NotFoundError, ScrapeMedia } from "@movie-web/providers";
|
import { MetaOutput, NotFoundError, ScrapeMedia } from "@movie-web/providers";
|
||||||
|
|
||||||
import { mwFetch } from "@/backend/helpers/fetch";
|
import { mwFetch } from "@/backend/helpers/fetch";
|
||||||
|
import { getTurnstileToken, isTurnstileInitialized } from "@/stores/turnstile";
|
||||||
|
|
||||||
let metaDataCache: MetaOutput[] | null = null;
|
let metaDataCache: MetaOutput[] | null = null;
|
||||||
|
let token: null | string = null;
|
||||||
|
|
||||||
export function setCachedMetadata(data: MetaOutput[]) {
|
export function setCachedMetadata(data: MetaOutput[]) {
|
||||||
metaDataCache = data;
|
metaDataCache = data;
|
||||||
@ -12,6 +14,20 @@ export function getCachedMetadata(): MetaOutput[] {
|
|||||||
return metaDataCache ?? [];
|
return metaDataCache ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getTokenIfValid(): null | string {
|
||||||
|
if (!token) return null;
|
||||||
|
const parts = token.split(".");
|
||||||
|
if (parts.length !== 3) return null;
|
||||||
|
try {
|
||||||
|
const parsedData = JSON.parse(atob(parts[2]));
|
||||||
|
if (!parsedData.exp) return token;
|
||||||
|
if (Date.now() < parsedData.exp) return token;
|
||||||
|
} catch {
|
||||||
|
// we dont care about parse errors
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
export async function fetchMetadata(base: string) {
|
export async function fetchMetadata(base: string) {
|
||||||
if (metaDataCache) return;
|
if (metaDataCache) return;
|
||||||
const data = await mwFetch<MetaOutput[][]>(`${base}/metadata`);
|
const data = await mwFetch<MetaOutput[][]>(`${base}/metadata`);
|
||||||
@ -67,8 +83,21 @@ export function makeProviderUrl(base: string) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function connectServerSideEvents<T>(url: string, endEvents: string[]) {
|
export async function connectServerSideEvents<T>(
|
||||||
const eventSource = new EventSource(url);
|
url: string,
|
||||||
|
endEvents: string[]
|
||||||
|
) {
|
||||||
|
// fetch token to use
|
||||||
|
let apiToken = getTokenIfValid();
|
||||||
|
if (!apiToken && isTurnstileInitialized()) {
|
||||||
|
apiToken = await getTurnstileToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert token, if its set
|
||||||
|
const parsedUrl = new URL(url);
|
||||||
|
if (apiToken) parsedUrl.searchParams.set("token", apiToken);
|
||||||
|
const eventSource = new EventSource(parsedUrl.toString());
|
||||||
|
|
||||||
let promReject: (reason?: any) => void;
|
let promReject: (reason?: any) => void;
|
||||||
let promResolve: (value: T) => void;
|
let promResolve: (value: T) => void;
|
||||||
const promise = new Promise<T>((resolve, reject) => {
|
const promise = new Promise<T>((resolve, reject) => {
|
||||||
@ -83,6 +112,10 @@ export function connectServerSideEvents<T>(url: string, endEvents: string[]) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
eventSource.addEventListener("token", (e) => {
|
||||||
|
token = JSON.parse(e.data);
|
||||||
|
});
|
||||||
|
|
||||||
eventSource.addEventListener("error", (err: MessageEvent<any>) => {
|
eventSource.addEventListener("error", (err: MessageEvent<any>) => {
|
||||||
eventSource.close();
|
eventSource.close();
|
||||||
if (err.data) {
|
if (err.data) {
|
||||||
|
@ -41,7 +41,7 @@ export function useEmbedScraping(
|
|||||||
try {
|
try {
|
||||||
if (providerApiUrl) {
|
if (providerApiUrl) {
|
||||||
const baseUrlMaker = makeProviderUrl(providerApiUrl);
|
const baseUrlMaker = makeProviderUrl(providerApiUrl);
|
||||||
const conn = connectServerSideEvents<EmbedOutput>(
|
const conn = await connectServerSideEvents<EmbedOutput>(
|
||||||
baseUrlMaker.scrapeEmbed(embedId, url),
|
baseUrlMaker.scrapeEmbed(embedId, url),
|
||||||
["completed", "noOutput"]
|
["completed", "noOutput"]
|
||||||
);
|
);
|
||||||
@ -105,7 +105,7 @@ export function useSourceScraping(sourceId: string | null, routerId: string) {
|
|||||||
try {
|
try {
|
||||||
if (providerApiUrl) {
|
if (providerApiUrl) {
|
||||||
const baseUrlMaker = makeProviderUrl(providerApiUrl);
|
const baseUrlMaker = makeProviderUrl(providerApiUrl);
|
||||||
const conn = connectServerSideEvents<SourcererOutput>(
|
const conn = await connectServerSideEvents<SourcererOutput>(
|
||||||
baseUrlMaker.scrapeSource(sourceId, scrapeMedia),
|
baseUrlMaker.scrapeSource(sourceId, scrapeMedia),
|
||||||
["completed", "noOutput"]
|
["completed", "noOutput"]
|
||||||
);
|
);
|
||||||
@ -146,7 +146,7 @@ export function useSourceScraping(sourceId: string | null, routerId: string) {
|
|||||||
try {
|
try {
|
||||||
if (providerApiUrl) {
|
if (providerApiUrl) {
|
||||||
const baseUrlMaker = makeProviderUrl(providerApiUrl);
|
const baseUrlMaker = makeProviderUrl(providerApiUrl);
|
||||||
const conn = connectServerSideEvents<EmbedOutput>(
|
const conn = await connectServerSideEvents<EmbedOutput>(
|
||||||
baseUrlMaker.scrapeEmbed(
|
baseUrlMaker.scrapeEmbed(
|
||||||
result.embeds[0].embedId,
|
result.embeds[0].embedId,
|
||||||
result.embeds[0].url
|
result.embeds[0].url
|
||||||
|
@ -156,7 +156,7 @@ export function useScrape() {
|
|||||||
if (providerApiUrl) {
|
if (providerApiUrl) {
|
||||||
startScrape();
|
startScrape();
|
||||||
const baseUrlMaker = makeProviderUrl(providerApiUrl);
|
const baseUrlMaker = makeProviderUrl(providerApiUrl);
|
||||||
const conn = connectServerSideEvents<RunOutput | "">(
|
const conn = await connectServerSideEvents<RunOutput | "">(
|
||||||
baseUrlMaker.scrapeAll(media),
|
baseUrlMaker.scrapeAll(media),
|
||||||
["completed", "noOutput"]
|
["completed", "noOutput"]
|
||||||
);
|
);
|
||||||
|
@ -10,6 +10,7 @@ import ReactDOM from "react-dom";
|
|||||||
import { HelmetProvider } from "react-helmet-async";
|
import { HelmetProvider } from "react-helmet-async";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { BrowserRouter, HashRouter } from "react-router-dom";
|
import { BrowserRouter, HashRouter } from "react-router-dom";
|
||||||
|
import Turnstile from "react-turnstile";
|
||||||
import { useAsync } from "react-use";
|
import { useAsync } from "react-use";
|
||||||
|
|
||||||
import { Button } from "@/components/buttons/Button";
|
import { Button } from "@/components/buttons/Button";
|
||||||
@ -30,16 +31,12 @@ import { useLanguageStore } from "@/stores/language";
|
|||||||
import { ProgressSyncer } from "@/stores/progress/ProgressSyncer";
|
import { ProgressSyncer } from "@/stores/progress/ProgressSyncer";
|
||||||
import { SettingsSyncer } from "@/stores/subtitles/SettingsSyncer";
|
import { SettingsSyncer } from "@/stores/subtitles/SettingsSyncer";
|
||||||
import { ThemeProvider } from "@/stores/theme";
|
import { ThemeProvider } from "@/stores/theme";
|
||||||
|
import { TurnstileProvider } from "@/stores/turnstile";
|
||||||
|
|
||||||
import { initializeChromecast } from "./setup/chromecast";
|
import { initializeChromecast } from "./setup/chromecast";
|
||||||
import { initializeOldStores } from "./stores/__old/migrations";
|
import { initializeOldStores } from "./stores/__old/migrations";
|
||||||
|
|
||||||
// initialize
|
// initialize
|
||||||
const key =
|
|
||||||
(window as any)?.__CONFIG__?.VITE_KEY ?? import.meta.env.VITE_KEY ?? null;
|
|
||||||
if (key) {
|
|
||||||
(window as any).initMW(conf().PROXY_URLS, key);
|
|
||||||
}
|
|
||||||
initializeChromecast();
|
initializeChromecast();
|
||||||
|
|
||||||
function LoadingScreen(props: { type: "user" | "lazy" }) {
|
function LoadingScreen(props: { type: "user" | "lazy" }) {
|
||||||
@ -148,6 +145,7 @@ function TheRouter(props: { children: ReactNode }) {
|
|||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
|
<TurnstileProvider />
|
||||||
<HelmetProvider>
|
<HelmetProvider>
|
||||||
<Suspense fallback={<LoadingScreen type="lazy" />}>
|
<Suspense fallback={<LoadingScreen type="lazy" />}>
|
||||||
<ThemeProvider applyGlobal>
|
<ThemeProvider applyGlobal>
|
||||||
|
@ -17,6 +17,7 @@ interface Config {
|
|||||||
NORMAL_ROUTER: boolean;
|
NORMAL_ROUTER: boolean;
|
||||||
BACKEND_URL: string;
|
BACKEND_URL: string;
|
||||||
DISALLOWED_IDS: string;
|
DISALLOWED_IDS: string;
|
||||||
|
TURNSTILE_KEY: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RuntimeConfig {
|
export interface RuntimeConfig {
|
||||||
@ -30,6 +31,7 @@ export interface RuntimeConfig {
|
|||||||
PROXY_URLS: string[];
|
PROXY_URLS: string[];
|
||||||
BACKEND_URL: string;
|
BACKEND_URL: string;
|
||||||
DISALLOWED_IDS: string[];
|
DISALLOWED_IDS: string[];
|
||||||
|
TURNSTILE_KEY: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const env: Record<keyof Config, undefined | string> = {
|
const env: Record<keyof Config, undefined | string> = {
|
||||||
@ -43,6 +45,7 @@ const env: Record<keyof Config, undefined | string> = {
|
|||||||
NORMAL_ROUTER: import.meta.env.VITE_NORMAL_ROUTER,
|
NORMAL_ROUTER: import.meta.env.VITE_NORMAL_ROUTER,
|
||||||
BACKEND_URL: import.meta.env.VITE_BACKEND_URL,
|
BACKEND_URL: import.meta.env.VITE_BACKEND_URL,
|
||||||
DISALLOWED_IDS: import.meta.env.VITE_DISALLOWED_IDS,
|
DISALLOWED_IDS: import.meta.env.VITE_DISALLOWED_IDS,
|
||||||
|
TURNSTILE_KEY: import.meta.env.VITE_TURNSTILE_KEY,
|
||||||
};
|
};
|
||||||
|
|
||||||
// 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)
|
||||||
@ -63,6 +66,7 @@ function getKey(key: keyof Config, defaultString?: string): string {
|
|||||||
|
|
||||||
export function conf(): RuntimeConfig {
|
export function conf(): RuntimeConfig {
|
||||||
const dmcaEmail = getKey("DMCA_EMAIL");
|
const dmcaEmail = getKey("DMCA_EMAIL");
|
||||||
|
const turnstileKey = getKey("TURNSTILE_KEY");
|
||||||
return {
|
return {
|
||||||
APP_VERSION,
|
APP_VERSION,
|
||||||
GITHUB_LINK,
|
GITHUB_LINK,
|
||||||
@ -75,6 +79,7 @@ export function conf(): RuntimeConfig {
|
|||||||
.split(",")
|
.split(",")
|
||||||
.map((v) => v.trim()),
|
.map((v) => v.trim()),
|
||||||
NORMAL_ROUTER: getKey("NORMAL_ROUTER", "false") === "true",
|
NORMAL_ROUTER: getKey("NORMAL_ROUTER", "false") === "true",
|
||||||
|
TURNSTILE_KEY: turnstileKey.length > 0 ? turnstileKey : null,
|
||||||
DISALLOWED_IDS: getKey("DISALLOWED_IDS", "")
|
DISALLOWED_IDS: getKey("DISALLOWED_IDS", "")
|
||||||
.split(",")
|
.split(",")
|
||||||
.map((v) => v.trim())
|
.map((v) => v.trim())
|
||||||
|
81
src/stores/turnstile/index.tsx
Normal file
81
src/stores/turnstile/index.tsx
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import Turnstile, { BoundTurnstileObject } from "react-turnstile";
|
||||||
|
import { create } from "zustand";
|
||||||
|
import { immer } from "zustand/middleware/immer";
|
||||||
|
|
||||||
|
import { conf } from "@/setup/config";
|
||||||
|
|
||||||
|
export interface TurnstileStore {
|
||||||
|
turnstile: BoundTurnstileObject | null;
|
||||||
|
cbs: ((token: string | null) => void)[];
|
||||||
|
setTurnstile(v: BoundTurnstileObject | null): void;
|
||||||
|
getToken(): Promise<string>;
|
||||||
|
processToken(token: string | null): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useTurnstileStore = create(
|
||||||
|
immer<TurnstileStore>((set, get) => ({
|
||||||
|
turnstile: null,
|
||||||
|
cbs: [],
|
||||||
|
processToken(token) {
|
||||||
|
const cbs = get().cbs;
|
||||||
|
cbs.forEach((fn) => fn(token));
|
||||||
|
set((s) => {
|
||||||
|
s.cbs = [];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getToken() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
set((s) => {
|
||||||
|
s.cbs = [
|
||||||
|
...s.cbs,
|
||||||
|
(token) => {
|
||||||
|
if (!token) reject(new Error("Failed to get token"));
|
||||||
|
else resolve(token);
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
setTurnstile(v) {
|
||||||
|
set((s) => {
|
||||||
|
s.turnstile = v;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
export function getTurnstile() {
|
||||||
|
return useTurnstileStore.getState().turnstile;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isTurnstileInitialized() {
|
||||||
|
return !!getTurnstile();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTurnstileToken() {
|
||||||
|
const turnstile = getTurnstile();
|
||||||
|
turnstile?.reset();
|
||||||
|
turnstile?.execute();
|
||||||
|
return useTurnstileStore.getState().getToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TurnstileProvider() {
|
||||||
|
const siteKey = conf().TURNSTILE_KEY;
|
||||||
|
const setTurnstile = useTurnstileStore((s) => s.setTurnstile);
|
||||||
|
const processToken = useTurnstileStore((s) => s.processToken);
|
||||||
|
if (!siteKey) return null;
|
||||||
|
return (
|
||||||
|
<Turnstile
|
||||||
|
sitekey={siteKey}
|
||||||
|
onLoad={(_widgetId, bound) => {
|
||||||
|
setTurnstile(bound);
|
||||||
|
}}
|
||||||
|
onError={() => {
|
||||||
|
processToken(null);
|
||||||
|
}}
|
||||||
|
onVerify={(token) => {
|
||||||
|
processToken(token);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user