import express, { json } from "express"; import cors from "cors"; import pLimit from "p-limit"; import BrowserPool from './pool/BrowserPool.js'; import { LRUCache } from 'lru-cache'; import fetch from 'node-fetch'; import { JSDOM } from 'jsdom'; import fs from 'fs/promises'; // Playwright with stealth plugin import { chromium } from 'playwright-extra'; import StealthPlugin from 'puppeteer-extra-plugin-stealth'; chromium.use(StealthPlugin()); const app = express(); const PORT = process.env.PORT || 3000; app.use(cors()); app.use(json()); const PROVIDERS = ["https://cdn.moviesapi.club"]; let browserPool; const cache = new LRUCache({ max: 500, ttl: 15 * 60 * 1000 }); const limit = pLimit(2); async function extractFirstIframeSrc(page, domain) { try { await page.waitForSelector('#player_iframe', { timeout: 15000 }); const src = await page.evaluate(() => { const iframe = document.getElementById('player_iframe'); if (!iframe) return null; let src = iframe.getAttribute('src') || ''; if (src.startsWith('//')) { src = `https:${src}`; } return src; }); if (!src) { throw new Error('First iframe src not found'); } console.log(`[${domain}] Found first iframe src: ${src}`); return src; } catch (error) { console.error(`[${domain}] Error finding first iframe:`, error); throw error; } } async function getVideoAndSubtitles(finalUrl) { try { const response = await fetch(finalUrl); if (!response.ok) { throw new Error(`HTTP error! Status: ${response.status}`); } const html = await response.text(); const dom = new JSDOM(html); const document = dom.window.document; const scriptTags = document.querySelectorAll('script'); let videoFileUrl = null; let subtitleSources = null; for (const script of scriptTags) { if (script.textContent.includes('new Playerjs({')) { const fileMatch = script.textContent.match(/file:\s*['"](.*?)['"]/); if (fileMatch && fileMatch[1]) { videoFileUrl = fileMatch[1]; } const subtitlesMatch = script.textContent.match(/default_subtitles\s*=\s*['"](.*?)['"]/); if (subtitlesMatch && subtitlesMatch[1]) { try { subtitleSources = JSON.parse(subtitlesMatch[1]); } catch (e) { subtitleSources = subtitlesMatch[1].split(',').map(s => s.trim()).filter(Boolean); } } if (videoFileUrl) { break; } } } return { videoFileUrl, subtitleSources }; } catch (error) { console.error('An error occurred during the process:', error); throw error; } } // 修改 scrapeProvider 函数 // 在 scrapeProvider 函数中添加调试代码 async function scrapeProvider_(domain, url, signal) { if (signal.aborted) throw new Error('Aborted'); console.log(`\n[${domain}] Starting scrape for URL: ${url}`); let browserInstance = null; try { browserInstance = await browserPool.get(); const browser = browserInstance.browser; // 创建初始页面 const page = await browser.newPage(); // 启用请求拦截 await page.route('**/*', route => { console.log(`[${domain}] Request: ${route.request().url()}`); route.continue(); }); await page.goto(url, { waitUntil: 'networkidle', timeout: 60000 }); // 打印页面HTML内容用于调试 const html = await page.content(); console.log(`[${domain}] Page HTML:\n${html}`); // 检查是否有Turnstile const hasTurnstile = await page.evaluate(() => { return document.querySelector('.cf-turnstile') !== null; }); console.log(`[${domain}] Has Turnstile: ${hasTurnstile}`); // 处理Turnstile if (hasTurnstile) { await handleSpecificTurnstile(page, domain); } // 获取第一个iframe的URL const firstIframeSrc = await extractFirstIframeSrc(page, domain); if (!firstIframeSrc) throw new Error('First iframe not found'); // 创建新页面来加载iframe内容 const iframePage = await browser.newPage(); await iframePage.goto(firstIframeSrc, { waitUntil: 'networkidle', timeout: 60000 }); // 打印iframe页面的HTML内容 const iframeHtml = await iframePage.content(); console.log(`[${domain}] Iframe HTML:\n${iframeHtml}`); // 处理可能存在的第二个Turnstile const iframeHasTurnstile = await iframePage.evaluate(() => { return document.querySelector('.cf-turnstile') !== null; }); console.log(`[${domain}] Iframe has Turnstile: ${iframeHasTurnstile}`); if (iframeHasTurnstile) { await handleSpecificTurnstile(iframePage, domain); } // 获取最终iframe的URL const finalIframeSrc = await extractFinalIframeSrc(iframePage, domain); if (!finalIframeSrc) throw new Error('Final iframe not found'); // 获取视频和字幕 const { videoFileUrl, subtitleSources } = await getVideoAndSubtitles(finalIframeSrc); if (!videoFileUrl) throw new Error("HLS URL not found"); return { source_domain: domain, hls_url: videoFileUrl, subtitles: subtitleSources, error: null }; } catch (error) { console.error(`[${domain}] Error in scrapeProvider: ${error.message}`); throw error; } finally { if (browserInstance) { console.log(`[${domain}] Releasing browser ${browserInstance.id} back to pool.`); await browserPool.release(browserInstance); } } } async function scrapeProvider__(domain, url, signal) { if (signal.aborted) throw new Error('Aborted'); console.log(`\n[${domain}] Starting scrape for URL: ${url}`); let browserInstance = null; try { browserInstance = await browserPool.get(); const browser = browserInstance.browser; // 创建初始页面 const page = await browser.newPage(); // 启用请求拦截 await page.route('**/*', route => { const requestUrl = route.request().url(); console.log(`[${domain}] Request: ${requestUrl}`); // 如果是 cloudnestra.com/rcp/ 的请求,记录响应但不拦截 if (requestUrl.includes('cloudnestra.com/rcp/')) { route.continue().then(response => { response.text().then(text => { console.log(`[${domain}] Cloudnestra response:`, text); // 将响应保存到页面属性中 page.cloudnestraResponse = text; }); }); } else { route.continue(); } }); await page.goto(url, { waitUntil: 'networkidle', timeout: 60000 }); // 检查是否有Turnstile const hasTurnstile = await page.evaluate(() => { return document.querySelector('.cf-turnstile') !== null; }); console.log(`[${domain}] Has Turnstile: ${hasTurnstile}`); // 处理Turnstile if (hasTurnstile) { await handleSpecificTurnstile(page, domain); } // 获取第一个iframe的URL const firstIframeSrc = await extractFirstIframeSrc(page, domain); if (!firstIframeSrc) throw new Error('First iframe not found'); // 创建新页面来加载iframe内容 const iframePage = await browser.newPage(); await iframePage.goto(firstIframeSrc, { waitUntil: 'networkidle', timeout: 60000 }); // 处理可能存在的第二个Turnstile const iframeHasTurnstile = await iframePage.evaluate(() => { return document.querySelector('.cf-turnstile') !== null; }); console.log(`[${domain}] Iframe has Turnstile: ${iframeHasTurnstile}`); if (iframeHasTurnstile) { await handleSpecificTurnstile(iframePage, domain); } // 获取最终iframe的URL const finalIframeSrc = await extractFinalIframeSrc(iframePage, domain); if (!finalIframeSrc) throw new Error('Final iframe not found'); // 检查是否是 cloudnestra.com/rcp/ 的URL if (finalIframeSrc.includes('cloudnestra.com/rcp/')) { // 使用保存的响应 const responseText = page.cloudnestraResponse; if (!responseText) throw new Error('No cloudnestra response found'); // 解析响应 try { const data = JSON.parse(responseText); if (data.file) { console.log(`[${domain}] Found video URL in cloudnestra response:`, data.file); return { source_domain: domain, hls_url: data.file, subtitles: data.subtitles || [], error: null }; } throw new Error("No video URL found in cloudnestra response"); } catch (e) { throw new Error("Failed to parse cloudnestra response"); } } // 如果不是 cloudnestra.com/rcp/ 的URL,使用原有的处理方式 const { videoFileUrl, subtitleSources } = await getVideoAndSubtitles(finalIframeSrc); if (!videoFileUrl) throw new Error("HLS URL not found"); return { source_domain: domain, hls_url: videoFileUrl, subtitles: subtitleSources, error: null }; } catch (error) { console.error(`[${domain}] Error in scrapeProvider: ${error.message}`); throw error; } finally { if (browserInstance) { console.log(`[${domain}] Releasing browser ${browserInstance.id} back to pool.`); await browserPool.release(browserInstance); } } } async function scrapeProvider6(domain, url, signal) { if (signal.aborted) throw new Error('Aborted'); console.log(`\n[${domain}] Starting scrape for URL: ${url}`); let browserInstance = null; try { browserInstance = await browserPool.get(); const browser = browserInstance.browser; // 创建初始页面 const page = await browser.newPage(); let videoResult = null; // 启用请求拦截 // await page.route('**/*', async (route) => { // const requestUrl = route.request().url(); // console.log(`[${domain}] Request: ${requestUrl}`); // // 如果是 cloudnestra.com/rcp/ 的请求,使用 getVideoAndSubtitles 解析 // if (requestUrl.includes('cloudnestra.com/rcp/')) { // try { // const { videoFileUrl, subtitleSources } = await getVideoAndSubtitles(requestUrl); // if (!videoFileUrl) throw new Error("HLS URL not found"); // videoResult = { // source_domain: domain, // hls_url: videoFileUrl, // subtitles: subtitleSources, // error: null // }; // route.abort(); // } catch (error) { // console.error(`[${domain}] Error processing cloudnestra request:`, error); // route.continue(); // } // } else { // route.continue(); // } // }); await page.route('**/*', async (route) => { const requestUrl = route.request().url(); console.log(`[${domain}] Request: ${requestUrl}`); // 如果是 cloudnestra.com/rcp/ 请求,输出它的内容 if (requestUrl.includes('cloudnestra.com/rcp/')) { console.log(`[${domain}] 🎯 检测到 cloudnestra.com/rcp/ 请求:`); console.log(`[${domain}] URL: ${requestUrl}`); try { // 获取请求的响应内容 const response = await route.fetch(); const content = await response.text(); console.log(`[${domain}] 响应状态: ${response.status()}`); console.log(`[${domain}] 响应头:`, response.headers()); console.log(`[${domain}] 响应内容长度: ${content.length}`); console.log(`[${domain}] 响应内容预览 (前500字符):`); console.log(content.substring(0, 500)); // 如果内容看起来像JSON,尝试解析 if (content.trim().startsWith('{') || content.trim().startsWith('[')) { try { const jsonContent = JSON.parse(content); console.log(`[${domain}] JSON内容:`, JSON.stringify(jsonContent, null, 2)); } catch (e) { console.log(`[${domain}] 不是有效的JSON格式`); } } // 继续正常的请求流程 route.continue(); } catch (error) { console.error(`[${domain}] 获取cloudnestra内容时出错:`, error); route.continue(); } } else { route.continue(); } }); await page.goto(url, { waitUntil: 'networkidle', timeout: 60000 }); // 如果已经从 cloudnestra 获取了视频链接,直接返回 if (videoResult) { return videoResult; } // 检查是否有Turnstile const hasTurnstile = await page.evaluate(() => { return document.querySelector('.cf-turnstile') !== null; }); console.log(`[${domain}] Has Turnstile: ${hasTurnstile}`); // 处理Turnstile if (hasTurnstile) { await handleSpecificTurnstile(page, domain); } // 获取第一个iframe的URL const firstIframeSrc = await extractFirstIframeSrc(page, domain); if (!firstIframeSrc) throw new Error('First iframe not found'); // 创建新页面来加载iframe内容 const iframePage = await browser.newPage(); await iframePage.goto(firstIframeSrc, { waitUntil: 'networkidle', timeout: 60000 }); // 处理可能存在的第二个Turnstile const iframeHasTurnstile = await iframePage.evaluate(() => { return document.querySelector('.cf-turnstile') !== null; }); console.log(`[${domain}] Iframe has Turnstile: ${iframeHasTurnstile}`); if (iframeHasTurnstile) { await handleSpecificTurnstile(iframePage, domain); } // 获取最终iframe的URL const finalIframeSrc = await extractFinalIframeSrc(iframePage, domain); if (!finalIframeSrc) throw new Error('Final iframe not found'); // 使用 getVideoAndSubtitles 函数获取视频和字幕 const { videoFileUrl, subtitleSources } = await getVideoAndSubtitles(finalIframeSrc); if (!videoFileUrl) throw new Error("HLS URL not found"); return { source_domain: domain, hls_url: videoFileUrl, subtitles: subtitleSources, error: null }; } catch (error) { console.error(`[${domain}] Error in scrapeProvider: ${error.message}`); throw error; } finally { if (browserInstance) { console.log(`[${domain}] Releasing browser ${browserInstance.id} back to pool.`); await browserPool.release(browserInstance); } } } async function scrapeProvider(domain, url, signal) { if (signal.aborted) throw new Error('Aborted'); console.log(`\n[${domain}] Starting scrape for URL: ${url}`); let browserInstance = null; let cloudnestraData = null; try { browserInstance = await browserPool.get(); const browser = browserInstance.browser; // 创建带有上下文的页面 const context = await browser.newContext({ userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', extraHTTPHeaders: { 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 'Accept-Language': 'en-US,en;q=0.5', 'Accept-Encoding': 'gzip, deflate, br', 'DNT': '1', 'Connection': 'keep-alive', 'Upgrade-Insecure-Requests': '1', }, viewport: { width: 1920, height: 1080 } }); // 移除自动化痕迹 await context.addInitScript(() => { Object.defineProperty(navigator, 'webdriver', { get: () => undefined }); Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3, 4, 5] }); Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] }); }); const page = await context.newPage(); // 启用请求拦截 await page.route('**/*', async (route) => { const requestUrl = route.request().url(); console.log(`[${domain}] Request: ${requestUrl}`); // 如果是 cloudnestra.com/rcp/ 请求,特殊处理 if (requestUrl.includes('cloudnestra.com/rcp/')) { console.log(`[${domain}] 🎯 检测到 cloudnestra.com/rcp/ 请求: ${requestUrl}`); try { // 添加随机延迟模拟人类行为 await new Promise(resolve => setTimeout(resolve, Math.random() * 1000 + 500)); // 使用真实的请求头获取内容 const response = await route.fetch({ headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'en-US,en;q=0.9', 'Referer': domain, 'Cache-Control': 'no-cache' } }); console.log(`[${domain}] Cloudnestra 响应状态: ${response.status()}`); if (response.status() === 200) { const content = await response.text(); console.log(`[${domain}] ✅ 成功获取 cloudnestra 内容,长度: ${content.length}`); // 尝试从内容中提取 src const srcMatch = content.match(/src\s*:\s*['"](.*?)['"]/); if (srcMatch && srcMatch[1]) { let finalUrl = srcMatch[1]; // 构造完整URL if (finalUrl.startsWith('/')) { const baseUrl = requestUrl.split('/rcp')[0]; finalUrl = `${baseUrl}${finalUrl}`; } finalUrl = finalUrl.replace('http://', 'https://'); console.log(`[${domain}] 从 cloudnestra 提取到的 URL: ${finalUrl}`); // 获取视频和字幕 const { videoFileUrl, subtitleSources } = await getVideoAndSubtitles(finalUrl); if (videoFileUrl) { cloudnestraData = { source_domain: domain, hls_url: videoFileUrl, subtitles: subtitleSources, error: null }; console.log(`[${domain}] 🎉 成功从 cloudnestra 获取视频: ${videoFileUrl}`); } } // 如果内容看起来像JSON,尝试直接解析 if (content.trim().startsWith('{') || content.trim().startsWith('[')) { try { const jsonContent = JSON.parse(content); if (jsonContent.file) { cloudnestraData = { source_domain: domain, hls_url: jsonContent.file, subtitles: jsonContent.subtitles || [], error: null }; console.log(`[${domain}] 🎉 从 JSON 响应获取视频: ${jsonContent.file}`); } } catch (e) { console.log(`[${domain}] 内容不是有效的JSON格式`); } } // 继续正常的请求流程 route.continue(); } else if (response.status() === 522) { console.log(`[${domain}] ❌ Cloudflare 连接超时 (522)`); route.continue(); } else if (response.status() === 403) { console.log(`[${domain}] ❌ 被 Cloudflare 拒绝访问 (403)`); route.continue(); } else { console.log(`[${domain}] ⚠️ 异常响应状态: ${response.status()}`); route.continue(); } } catch (error) { console.error(`[${domain}] ❌ 处理 cloudnestra 请求失败:`, error.message); route.continue(); } } else { route.continue(); } }); await page.goto(url, { waitUntil: 'networkidle', timeout: 60000 }); // 如果已经从 cloudnestra 获取了视频链接,直接返回 if (cloudnestraData) { console.log(`[${domain}] 🎯 使用 cloudnestra 数据`); return cloudnestraData; } // 继续原有的处理流程... // 检查是否有Turnstile const hasTurnstile = await page.evaluate(() => { return document.querySelector('.cf-turnstile') !== null; }); console.log(`[${domain}] Has Turnstile: ${hasTurnstile}`); // 处理Turnstile if (hasTurnstile) { await handleSpecificTurnstile(page, domain); } // 获取第一个iframe的URL const firstIframeSrc = await extractFirstIframeSrc(page, domain); if (!firstIframeSrc) throw new Error('First iframe not found'); // 创建新页面来加载iframe内容 const iframePage = await context.newPage(); await iframePage.goto(firstIframeSrc, { waitUntil: 'networkidle', timeout: 60000 }); // 处理可能存在的第二个Turnstile const iframeHasTurnstile = await iframePage.evaluate(() => { return document.querySelector('.cf-turnstile') !== null; }); console.log(`[${domain}] Iframe has Turnstile: ${iframeHasTurnstile}`); if (iframeHasTurnstile) { await handleSpecificTurnstile(iframePage, domain); } // 获取最终iframe的URL const finalIframeSrc = await extractFinalIframeSrc(iframePage, domain); if (!finalIframeSrc) throw new Error('Final iframe not found'); // 使用 getVideoAndSubtitles 函数获取视频和字幕 const { videoFileUrl, subtitleSources } = await getVideoAndSubtitles(finalIframeSrc); if (!videoFileUrl) throw new Error("HLS URL not found"); return { source_domain: domain, hls_url: videoFileUrl, subtitles: subtitleSources, error: null }; } catch (error) { console.error(`[${domain}] Error in scrapeProvider: ${error.message}`); throw error; } finally { if (browserInstance) { console.log(`[${domain}] Releasing browser ${browserInstance.id} back to pool.`); // 关闭上下文 if (typeof context !== 'undefined') { await context.close(); } await browserPool.release(browserInstance); } } } async function scrapeProvider4(domain, url, signal) { if (signal.aborted) throw new Error('Aborted'); console.log(`\n[${domain}] Starting scrape for URL: ${url}`); let browserInstance = null; try { browserInstance = await browserPool.get(); const browser = browserInstance.browser; // 创建初始页面 const page = await browser.newPage(); let videoResult = null; // 启用请求拦截 await page.route('**/*', async (route) => { const requestUrl = route.request().url(); console.log(`[${domain}] Request: ${requestUrl}`); // 如果是 cloudnestra.com/rcp/ 的请求,使用 getVideoAndSubtitles 解析 if (requestUrl.includes('cloudnestra.com/rcp/')) { try { const { videoFileUrl, subtitleSources } = await getVideoAndSubtitles(requestUrl); if (!videoFileUrl) throw new Error("HLS URL not found"); videoResult = { source_domain: domain, hls_url: videoFileUrl, subtitles: subtitleSources, error: null }; route.abort(); // 中止所有后续请求 await page.unroute('**/*'); } catch (error) { console.error(`[${domain}] Error processing cloudnestra request:`, error); route.abort(); // 中止所有后续请求 await page.unroute('**/*'); } } else { route.abort(); // 中止所有非 cloudnestra.com/rcp/ 的请求 } }); // 等待 videoResult 被设置 await page.waitForFunction( () => videoResult !== null, { timeout: 30000 } ).catch(() => { throw new Error('Timeout waiting for cloudnestra.com/rcp/ request'); }); return videoResult; } catch (error) { console.error(`[${domain}] Error in scrapeProvider: ${error.message}`); throw error; } finally { if (browserInstance) { console.log(`[${domain}] Releasing browser ${browserInstance.id} back to pool.`); await browserPool.release(browserInstance); } } } async function scrapeProvider5(domain, url, signal) { if (signal.aborted) throw new Error('Aborted'); console.log(`\n[${domain}] Starting scrape for URL: ${url}`); let browserInstance = null; try { browserInstance = await browserPool.get(); const browser = browserInstance.browser; // 创建初始页面 const page = await browser.newPage(); let videoResult = null; // 启用请求拦截 await page.route('**/*', async (route) => { const requestUrl = route.request().url(); console.log(`[${domain}] Request: ${requestUrl}`); // 如果是 cloudnestra.com/rcp/ 的请求 if (requestUrl.includes('cloudnestra.com/rcp/')) { try { // 获取响应 const response = await route.fetch(); const html = await response.text(); // 使用正则表达式提取 src 值 const proMatch = html.match(/src\s*:\s*'([^']+)'/); let finalUrl = proMatch?.[1]; if (!finalUrl) { throw new Error('Could not find src in response'); } // 确保使用 HTTPS 协议 const baseUrl = requestUrl.split('/rcp')[0]; // 获取基础 URL finalUrl = `${baseUrl}${finalUrl}`; // 拼接完整 URL finalUrl = finalUrl.replace('http://', 'https://'); // 确保使用 HTTPS console.log('Final URL:', finalUrl); // 调用 getVideoAndSubtitles const { videoFileUrl, subtitleSources } = await getVideoAndSubtitles(finalUrl); if (!videoFileUrl) throw new Error("HLS URL not found"); videoResult = { source_domain: domain, hls_url: videoFileUrl, subtitles: subtitleSources, error: null }; // 中止所有后续请求 await page.unroute('**/*'); route.abort(); } catch (error) { console.error(`[${domain}] Error processing cloudnestra request:`, error); route.abort(); await page.unroute('**/*'); } } else { route.abort(); } }); // 等待 videoResult 被设置 await page.waitForFunction( () => videoResult !== null, { timeout: 30000 } ).catch(() => { throw new Error('Timeout waiting for cloudnestra.com/rcp/ request'); }); return videoResult; } catch (error) { console.error(`[${domain}] Error in scrapeProvider: ${error.message}`); throw error; } finally { if (browserInstance) { console.log(`[${domain}] Releasing browser ${browserInstance.id} back to pool.`); await browserPool.release(browserInstance); } } } // 修改 handleSpecificTurnstile 函数 async function handleSpecificTurnstile(page, domain) { try { console.log(`[${domain}] Checking for Turnstile...`); // 检查是否存在Turnstile const turnstileExists = await page.waitForSelector('.cf-turnstile', { timeout: 10000, state: 'attached' }).then(() => true).catch(() => false); if (!turnstileExists) { console.log(`[${domain}] No Turnstile detected`); return; } console.log(`[${domain}] Turnstile detected - bypassing...`); // 等待Turnstile加载 await page.waitForSelector('.cf-turnstile', { timeout: 10000, state: 'visible' }); // 直接触发回调 await page.evaluate(() => { const mockToken = 'mock-token-' + Math.random().toString(36).substring(2); if (typeof window.cftCallback === 'function') { window.cftCallback(mockToken); } // 提交表单 const form = document.createElement('form'); form.method = 'POST'; form.action = '/rcp_verify'; const input = document.createElement('input'); input.type = 'hidden'; input.name = 'token'; input.value = mockToken; form.appendChild(input); document.body.appendChild(form); form.submit(); }); // 等待页面响应 await page.waitForNavigation({ waitUntil: 'networkidle', // 使用Playwright支持的等待策略 timeout: 30000 }); console.log(`[${domain}] Turnstile bypass completed`); } catch (error) { console.warn(`[${domain}] Turnstile bypass warning: ${error.message}`); } } // 修改 extractFinalIframeSrc 函数 async function extractFinalIframeSrc(page, domain) { try { // 等待动态内容加载 await page.waitForFunction(() => { return document.querySelectorAll('script, iframe').length > 0; }, { timeout: 15000 }); return await page.evaluate(() => { // Method 1: 检查直接iframe const iframe = document.querySelector('iframe:not([style*="display:none"])'); if (iframe?.src) return iframe.src; // Method 2: 检查脚本中的iframe URL const scripts = Array.from(document.querySelectorAll('script')); for (const script of scripts) { if (!script.textContent) continue; // 查找loadIframe模式 if (script.textContent.includes('loadIframe')) { const srcMatch = script.textContent.match(/src:\s*['"](.*?)['"]/); if (srcMatch) return new URL(srcMatch[1], window.location.href).href; } // 查找直接URL模式 const urlMatch = script.textContent.match(/(https?:)?\/\/[^'"]+embed[^'"]*/); if (urlMatch) { return urlMatch[0].startsWith('http') ? urlMatch[0] : `https:${urlMatch[0]}`; } } return null; }); } catch (error) { console.error(`[${domain}] Error finding final iframe: ${error.message}`); return null; } } app.get("/extract", async (req, res) => { const type = req.query.type || "movie"; const tmdb_id = req.query.tmdb_id; const season = req.query.season ? parseInt(req.query.season) : undefined; const episode = req.query.episode ? parseInt(req.query.episode) : undefined; if (!tmdb_id) { return res.status(400).json({ success: false, error: "tmdb_id is required" }); } if (type === "tv" && (season == null || episode == null)) { return res.status(400).json({ success: false, error: "season and episode are required" }); } const cacheKey = JSON.stringify(req.query); const cached = cache.get(cacheKey); if (cached) { console.log("Serving from cache"); return res.json(cached); } const urls = PROVIDERS.reduce((acc, domain) => { acc[domain] = type === "tv" ? `${domain}/embed/tv?tmdb=${tmdb_id}&season=${season}&episode=${episode}` : `${domain}/embed/movie/${tmdb_id}`; return acc; }, {}); const controller = new AbortController(); const signal = controller.signal; try { const promises = Object.entries(urls).map(([domain, url]) => limit(() => scrapeProvider(domain, url, signal)) ); const firstSuccessfulResult = await Promise.any(promises); controller.abort(); const response = { success: true, result: firstSuccessfulResult }; cache.set(cacheKey, response); res.json(response); } catch (err) { if (err instanceof AggregateError) { console.error("All providers failed to find a link."); res.status(404).json({ success: false, error: "Could not find the video from any provider." }); } else { console.error("An unexpected server error occurred:", err); res.status(500).json({ success: false, error: "Unexpected server error" }); } } }); (async () => { try { browserPool = new BrowserPool({ chromium: chromium, minSize: 1, maxSize: 5, maxUsage: 100, launchOptions: { headless: true, args: [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--disable-gpu', '--disable-extensions', '--disable-notifications', '--disable-infobars', '--disable-web-security', '--disable-features=IsolateOrigins,site-per-process' ] } }); await browserPool.initialize(); console.log("Browser pool initialized successfully."); app.listen(PORT, () => console.log(`🚀 Universal Video Extractor running at http://localhost:${PORT}`)); } catch (error) { console.error("Failed to initialize browser pool:", error); process.exit(1); } })(); process.on("SIGINT", async () => { console.log("Shutting down gracefully..."); if (browserPool) await browserPool.shutdown(); process.exit(0); }); process.on("SIGTERM", async () => { console.log("Shutting down gracefully..."); if (browserPool) await browserPool.shutdown(); process.exit(0); });