pstream is dead; long live pstream taciturnaxolotl.github.io/pstream-ng/
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

add turnstile to skip api

Pas 1cdaed56 ac5d4443

+170 -1
+10 -1
src/components/player/hooks/useSkipTime.ts
··· 3 3 import { usePlayerMeta } from "@/components/player/hooks/usePlayerMeta"; 4 4 import { conf } from "@/setup/config"; 5 5 import { usePreferencesStore } from "@/stores/preferences"; 6 + import { getTurnstileToken } from "@/utils/turnstile"; 6 7 7 8 // Thanks Nemo for this API 8 9 const BASE_URL = "https://fed-skips.pstream.mov"; ··· 20 21 if (!febboxKey) return; 21 22 22 23 try { 24 + const turnstileToken = await getTurnstileToken( 25 + "0x4AAAAAAB6ocCCpurfWRZyC", 26 + ); 27 + 23 28 const apiUrl = `${BASE_URL}/${meta.imdbId}/${meta.season?.number}/${meta.episode?.number}`; 24 - const response = await fetch(apiUrl); 29 + const response = await fetch(apiUrl, { 30 + headers: { 31 + "cf-turnstile-response": turnstileToken, 32 + }, 33 + }); 25 34 26 35 if (!response.ok) { 27 36 if (response.status === 500 && retries < MAX_RETRIES) {
+160
src/utils/turnstile.ts
··· 1 + /** 2 + * Cloudflare Turnstile utility for handling invisible CAPTCHA verification 3 + */ 4 + 5 + /** 6 + * Loads the Cloudflare Turnstile script if not already loaded 7 + */ 8 + function loadTurnstileScript(): Promise<void> { 9 + return new Promise((resolve, reject) => { 10 + // Check if Turnstile is already loaded 11 + if ((window as any).turnstile) { 12 + resolve(); 13 + return; 14 + } 15 + 16 + // Check if script is already being loaded 17 + if ( 18 + document.querySelector( 19 + 'script[src*="challenges.cloudflare.com/turnstile"]', 20 + ) 21 + ) { 22 + // Wait for it to load 23 + const checkLoaded = () => { 24 + if ((window as any).turnstile) { 25 + resolve(); 26 + } else { 27 + setTimeout(checkLoaded, 100); 28 + } 29 + }; 30 + checkLoaded(); 31 + return; 32 + } 33 + 34 + const script = document.createElement("script"); 35 + script.src = "https://challenges.cloudflare.com/turnstile/v0/api.js"; 36 + script.async = true; 37 + script.defer = true; 38 + 39 + script.onload = () => resolve(); 40 + script.onerror = () => reject(new Error("Failed to load Turnstile script")); 41 + 42 + document.head.appendChild(script); 43 + }); 44 + } 45 + 46 + /** 47 + * Creates an invisible Turnstile widget and returns a promise that resolves with the token 48 + * @param sitekey The Turnstile site key 49 + * @param timeout Optional timeout in milliseconds (default: 30000) 50 + * @returns Promise that resolves with the Turnstile token 51 + */ 52 + export async function getTurnstileToken( 53 + sitekey: string, 54 + timeout: number = 30000, 55 + ): Promise<string> { 56 + // Only run in browser environment 57 + if (typeof window === "undefined") { 58 + throw new Error("Turnstile verification requires browser environment"); 59 + } 60 + 61 + try { 62 + // Load Turnstile script 63 + await loadTurnstileScript(); 64 + 65 + // Create a hidden container for the Turnstile widget 66 + const container = document.createElement("div"); 67 + container.style.position = "absolute"; 68 + container.style.left = "-9999px"; 69 + container.style.top = "-9999px"; 70 + container.style.width = "1px"; 71 + container.style.height = "1px"; 72 + container.style.overflow = "hidden"; 73 + container.style.opacity = "0"; 74 + container.style.pointerEvents = "none"; 75 + 76 + document.body.appendChild(container); 77 + 78 + return new Promise<string>((resolve, reject) => { 79 + let widgetId: string | undefined; 80 + let timeoutId: any; 81 + 82 + const cleanup = () => { 83 + if (timeoutId) clearTimeout(timeoutId); 84 + if (widgetId && (window as any).turnstile) { 85 + try { 86 + (window as any).turnstile.remove(widgetId); 87 + } catch (e) { 88 + // Ignore errors during cleanup 89 + } 90 + } 91 + if (container.parentNode) { 92 + container.parentNode.removeChild(container); 93 + } 94 + }; 95 + 96 + // Set up timeout 97 + timeoutId = setTimeout(() => { 98 + cleanup(); 99 + reject(new Error("Turnstile verification timed out")); 100 + }, timeout); 101 + 102 + try { 103 + // Render the Turnstile widget 104 + widgetId = (window as any).turnstile.render(container, { 105 + sitekey, 106 + callback: (token: string) => { 107 + cleanup(); 108 + resolve(token); 109 + }, 110 + "error-callback": (error: string) => { 111 + cleanup(); 112 + reject(new Error(`Turnstile error: ${error}`)); 113 + }, 114 + "expired-callback": () => { 115 + cleanup(); 116 + reject(new Error("Turnstile token expired")); 117 + }, 118 + }); 119 + } catch (error) { 120 + cleanup(); 121 + reject(new Error(`Failed to render Turnstile widget: ${error}`)); 122 + } 123 + }); 124 + } catch (error) { 125 + throw new Error(`Turnstile verification failed: ${error}`); 126 + } 127 + } 128 + 129 + /** 130 + * Validates a Turnstile token by making a request to Cloudflare's verification endpoint 131 + * @param token The Turnstile token to validate 132 + * @param secret The Turnstile secret key (server-side only) 133 + * @returns Promise that resolves with validation result 134 + */ 135 + export async function validateTurnstileToken( 136 + token: string, 137 + secret: string, 138 + ): Promise<boolean> { 139 + try { 140 + const response = await fetch( 141 + "https://challenges.cloudflare.com/turnstile/v0/siteverify", 142 + { 143 + method: "POST", 144 + headers: { 145 + "Content-Type": "application/x-www-form-urlencoded", 146 + }, 147 + body: new URLSearchParams({ 148 + secret, 149 + response: token, 150 + }), 151 + }, 152 + ); 153 + 154 + const result = await response.json(); 155 + return result.success === true; 156 + } catch (error) { 157 + console.error("Turnstile validation error:", error); 158 + return false; 159 + } 160 + }