add turnstile to skip api

This commit is contained in:
Pas 2025-11-02 10:54:35 -07:00
parent ac5d4443b1
commit 1cdaed5625
2 changed files with 170 additions and 1 deletions

View file

@ -3,6 +3,7 @@ import { useEffect, useState } from "react";
import { usePlayerMeta } from "@/components/player/hooks/usePlayerMeta";
import { conf } from "@/setup/config";
import { usePreferencesStore } from "@/stores/preferences";
import { getTurnstileToken } from "@/utils/turnstile";
// Thanks Nemo for this API
const BASE_URL = "https://fed-skips.pstream.mov";
@ -20,8 +21,16 @@ export function useSkipTime() {
if (!febboxKey) return;
try {
const turnstileToken = await getTurnstileToken(
"0x4AAAAAAB6ocCCpurfWRZyC",
);
const apiUrl = `${BASE_URL}/${meta.imdbId}/${meta.season?.number}/${meta.episode?.number}`;
const response = await fetch(apiUrl);
const response = await fetch(apiUrl, {
headers: {
"cf-turnstile-response": turnstileToken,
},
});
if (!response.ok) {
if (response.status === 500 && retries < MAX_RETRIES) {

160
src/utils/turnstile.ts Normal file
View file

@ -0,0 +1,160 @@
/**
* Cloudflare Turnstile utility for handling invisible CAPTCHA verification
*/
/**
* Loads the Cloudflare Turnstile script if not already loaded
*/
function loadTurnstileScript(): Promise<void> {
return new Promise((resolve, reject) => {
// Check if Turnstile is already loaded
if ((window as any).turnstile) {
resolve();
return;
}
// Check if script is already being loaded
if (
document.querySelector(
'script[src*="challenges.cloudflare.com/turnstile"]',
)
) {
// Wait for it to load
const checkLoaded = () => {
if ((window as any).turnstile) {
resolve();
} else {
setTimeout(checkLoaded, 100);
}
};
checkLoaded();
return;
}
const script = document.createElement("script");
script.src = "https://challenges.cloudflare.com/turnstile/v0/api.js";
script.async = true;
script.defer = true;
script.onload = () => resolve();
script.onerror = () => reject(new Error("Failed to load Turnstile script"));
document.head.appendChild(script);
});
}
/**
* Creates an invisible Turnstile widget and returns a promise that resolves with the token
* @param sitekey The Turnstile site key
* @param timeout Optional timeout in milliseconds (default: 30000)
* @returns Promise that resolves with the Turnstile token
*/
export async function getTurnstileToken(
sitekey: string,
timeout: number = 30000,
): Promise<string> {
// Only run in browser environment
if (typeof window === "undefined") {
throw new Error("Turnstile verification requires browser environment");
}
try {
// Load Turnstile script
await loadTurnstileScript();
// Create a hidden container for the Turnstile widget
const container = document.createElement("div");
container.style.position = "absolute";
container.style.left = "-9999px";
container.style.top = "-9999px";
container.style.width = "1px";
container.style.height = "1px";
container.style.overflow = "hidden";
container.style.opacity = "0";
container.style.pointerEvents = "none";
document.body.appendChild(container);
return new Promise<string>((resolve, reject) => {
let widgetId: string | undefined;
let timeoutId: any;
const cleanup = () => {
if (timeoutId) clearTimeout(timeoutId);
if (widgetId && (window as any).turnstile) {
try {
(window as any).turnstile.remove(widgetId);
} catch (e) {
// Ignore errors during cleanup
}
}
if (container.parentNode) {
container.parentNode.removeChild(container);
}
};
// Set up timeout
timeoutId = setTimeout(() => {
cleanup();
reject(new Error("Turnstile verification timed out"));
}, timeout);
try {
// Render the Turnstile widget
widgetId = (window as any).turnstile.render(container, {
sitekey,
callback: (token: string) => {
cleanup();
resolve(token);
},
"error-callback": (error: string) => {
cleanup();
reject(new Error(`Turnstile error: ${error}`));
},
"expired-callback": () => {
cleanup();
reject(new Error("Turnstile token expired"));
},
});
} catch (error) {
cleanup();
reject(new Error(`Failed to render Turnstile widget: ${error}`));
}
});
} catch (error) {
throw new Error(`Turnstile verification failed: ${error}`);
}
}
/**
* Validates a Turnstile token by making a request to Cloudflare's verification endpoint
* @param token The Turnstile token to validate
* @param secret The Turnstile secret key (server-side only)
* @returns Promise that resolves with validation result
*/
export async function validateTurnstileToken(
token: string,
secret: string,
): Promise<boolean> {
try {
const response = await fetch(
"https://challenges.cloudflare.com/turnstile/v0/siteverify",
{
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
secret,
response: token,
}),
},
);
const result = await response.json();
return result.success === true;
} catch (error) {
console.error("Turnstile validation error:", error);
return false;
}
}