const express = require('express'); const path = require('path'); const fs = require('fs'); const puppeteer = require('puppeteer'); const app = express(); const PORT = 7860; // 中间件 app.use(express.json({ limit: '40mb' })); app.use(express.static('.')); // CORS支持 app.use((req, res, next) => { res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization'); if (req.method === 'OPTIONS') { res.sendStatus(200); } else { next(); } }); async function redr(url, method = "GET", postData = null) { const browser = await puppeteer.launch({ executablePath: "/opt/google/chrome/chrome", headless: true, }); const page = await browser.newPage(); await page.setExtraHTTPHeaders({ "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "accept-encoding": "gzip, deflate, br, zstd", "accept-language": "en-US,en;q=0.9", "user-agent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Mobile Safari/537.36" }); let response; try { if (method === "POST" && postData) { response = await page.goto(url, { waitUntil: "load", timeout: 0, method: "POST", postData: JSON.stringify(postData), headers: { "Content-Type": "application/json", }, }); } else { response = await page.goto(url, { waitUntil: "load", timeout: 10000 }); } if (!response) throw new Error("No response received"); const cookiesArr = await page.cookies(); const cookies = cookiesArr.map(c => `${c.name}=${c.value}`).join("; "); return { finalUrl: page.url(), status: response.status(), headers: response.headers(), cookies: cookies, }; } catch (error) { console.error("Error scraping page:", error); return { error: error.message }; } finally { await page.close(); } } app.get('/location', async (req, res) => { const url = req.query.url; const method = req.query.method || 'GET'; if (!url) { return res.status(400).send('URL query parameter is required'); } try { const pageContent = await redr(url, method); res.json(pageContent); } catch (error) { res.status(500).send('Failed to scrape the page'); } }); async function fzdl(url) { const browser = await puppeteer.launch({ executablePath: "/opt/google/chrome/chrome", headless: true, }); const page = await browser.newPage(); await page.setExtraHTTPHeaders({ "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8", "accept-language": "en-US,en;q=0.9", "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36" }); try { let dllink; if (url.includes("/generate-link")) { const parsedUrl = new URL("https://fzmovie.co.za" + url); dllink = parsedUrl.searchParams.get("link"); if (!dllink) throw new Error("No 'link' query parameter found"); } else { await page.goto(url, { waitUntil: "networkidle2" }); await page.waitForSelector("#downloadBtn", { visible: true, timeout: 15000 }); dllink = await page.$eval("#downloadBtn", el => el.href); } const cookiesArr = await page.cookies(); const cookies = cookiesArr.map(c => `${c.name}=${c.value}`).join("; "); return { url, dllink, cookies }; } catch (err) { console.error("Error fetching download link or cookies:", err.message); return { error: err.message }; } finally { await browser.close(); } } app.get('/fzdl', async (req, res) => { const url = req.query.url; if (!url) { return res.status(400).send('URL query parameter is required'); } try { const pakiii = await fzdl(url) res.json(pakiii); } catch (error) { res.status(500).send('Failed to scrape the page'); } }); async function captureDirectDownloadLink(url, opts = {}) { const { chromePath = '/opt/google/chrome/chrome', headless = 'new', waitForButtonMs = 60000 } = opts; let browser = null; let page = null; try { browser = await puppeteer.launch({ executablePath: chromePath, headless, args: [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-web-security', '--disable-gpu', '--disable-blink-features=AutomationControlled', '--disable-features=IsolateOrigins,site-per-process' ], }); page = await browser.newPage(); await page.setViewport({ width: 1280, height: 900 }); await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36'); await page.evaluateOnNewDocument(() => { Object.defineProperty(navigator, 'webdriver', { get: () => undefined }); Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3, 4, 5] }); Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] }); window.chrome = { runtime: {} }; window.DisableDevtool = function() {}; window.qajblusk = false; }); await page.setRequestInterception(true); page.on('request', req => { const rt = req.resourceType(); if (['image', 'font'].includes(rt)) { req.abort(); } else { req.continue(); } }); page.on('console', msg => console.log('PAGE LOG:', msg.text())); page.on('pageerror', err => console.error('PAGE ERROR:', err)); page.on('response', async response => { if (response.url() === page.url() && response.request().method() === 'POST') { console.log('📡 API Response Status:', response.status()); try { const text = await response.text().catch(() => '[BINARY]'); console.log('📡 API Response Body:', text.substring(0, 500)); } catch (e) { console.log('📡 API Response: Could not read body'); } } }); await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 }); await page.waitForFunction(() => document.readyState === 'complete', { timeout: 30000 }).catch(() => {}); await new Promise(resolve => setTimeout(resolve, 2000)); const directBtnId = await page.evaluate(() => { const buttons = Array.from(document.querySelectorAll('button')); const targetButton = buttons.find(btn => btn.textContent.trim() === 'Direct Download 2' ); return targetButton ? targetButton.id : null; }); if (!directBtnId) { throw new Error('No "Direct Download 2" button found on page!'); } console.log('✅ Found "Direct Download 2" button with ID:', directBtnId); console.log(`🖱️ Clicking "Direct Download 2" button (ID: ${directBtnId})...`); const button = await page.$(`#${directBtnId}`); if (button) { await button.click(); } else { throw new Error('Button disappeared before click'); } console.log('⏳ Waiting for API request to complete...'); await page.waitForFunction( id => { const el = document.getElementById(id); if (!el) return true; const span = el.querySelector('.download-text'); return !span || span.textContent.trim() !== 'Processing...'; }, { timeout: 45000, polling: 500 }, directBtnId ); console.log('✅ Processing finished. Listening for download...'); const newPagePromise = new Promise(resolve => { const timeout = setTimeout(() => { resolve(null); }, 25000); browser.once('targetcreated', async target => { clearTimeout(timeout); if (target.type() !== 'page') { resolve(null); return; } try { const popupPage = await target.page(); if (!popupPage) { resolve(null); return; } await popupPage.waitForNavigation({ waitUntil: 'networkidle0', timeout: 30000 }).catch(() => {}); resolve(popupPage); } catch (err) { console.error('Error handling new tab:', err); resolve(null); } }); }); const newPage = await newPagePromise; let finalUrl; if (newPage) { finalUrl = newPage.url(); console.log('🎉 Captured Download URL from NEW TAB:', finalUrl); await newPage.close(); } else { console.log('⚠️ No popup detected. Checking main tab navigation...'); await new Promise(resolve => setTimeout(resolve, 3000)); const currentUrl = page.url(); if (currentUrl !== url && !currentUrl.includes('/images/fordev.jpg') && !currentUrl.startsWith('about:')) { finalUrl = currentUrl; console.log('✅ Fallback success: Download URL from MAIN TAB:', finalUrl); } else { throw new Error('❌ No new tab OR main tab navigation detected after click.'); } } await browser.close(); if (!finalUrl || finalUrl === 'about:blank' || finalUrl.includes('fordev.jpg')) { throw new Error('❌ Download URL is invalid or blocked.'); } return { success: true, url: finalUrl }; } catch (err) { console.error('❌ captureDirectDownloadLink error:', err); if (page) try { await page.close(); } catch {} if (browser) try { await browser.close(); } catch {} return { success: false, error: String(err.message || err) }; } } app.get('/api/bypass', async (req, res) => { const { url } = req.query; if (!url) return res.status(400).json({ error: 'URL is required' }); try { const result = await captureDirectDownloadLink(url, { chromePath: '/opt/google/chrome/chrome', headless: 'new', buttonSelector: '#gdriveButton', fallbackSelector: '.google-download', waitForButtonMs: 60000, waitForPostMs: 20000, }); if (!result.success) { return res.status(500).json({ error: result.error || 'failed' }); } // Return cookie header and payload return res.json({ url: result }); } catch (err) { console.error('/api/bypass error:', err); return res.status(500).json({ error: 'Internal error' }); } }); const PRESETS = { "Mobile (375x667)": { width: 375, height: 667 }, "Tablet (768x1024)": { width: 768, height: 1024 }, "PC (1366x768)": { width: 1366, height: 768 }, "Full Screenshot": { width: 1920, height: 1080 } }; async function takeScreenshot(url, device) { let browser; const preset = PRESETS[device] || PRESETS["PC (1366x768)"]; try { browser = await puppeteer.launch({ executablePath: "/opt/google/chrome/chrome", args: ['--no-sandbox', '--disable-setuid-sandbox'], headless: 'new' }); const page = await browser.newPage(); await page.setViewport({ width: preset.width, height: preset.height, deviceScaleFactor: 1 }); await page.goto(url, { waitUntil: 'networkidle0', timeout: 30000 }); let screenshotBuffer; if (device === "Full Screenshot") { const bodyHandle = await page.$('body'); const boundingBox = await bodyHandle.boundingBox(); await bodyHandle.dispose(); const fullHeight = boundingBox ? boundingBox.height : preset.height; await page.setViewport({ width: preset.width, height: fullHeight, deviceScaleFactor: 1 }); screenshotBuffer = await page.screenshot({ fullPage: true, type: 'png' }); } else { screenshotBuffer = await page.screenshot({ type: 'png' }); } return screenshotBuffer; } catch (error) { console.error('Puppeteer operation failed:', error); throw new Error('Screenshot failed: ' + error.message); } finally { if (browser) { await page.close(); } } } app.get('/api/screenshot', async (req, res) => { const { url, device } = req.query; if (!url) { return res.status(400).json({ error: 'URL query parameter is required' }); } const targetUrl = url.startsWith('http') ? url : `https://${url}`; try { const screenshotBuffer = await takeScreenshot(targetUrl, device); const base64Image = screenshotBuffer.toString('base64'); return res.json({ success: true, device: device || "PC (1366x768)", screenshotBase64: base64Image }); } catch (err) { console.error('/api/screenshot error:', err.message); return res.status(500).json({ error: err.message || 'Internal server error during screenshot capture' }); } }); // 健康检查端点 app.get('/', (req, res) => { res.json({ status: 'hii', timestamp: new Date().toISOString() }); }); // 启动服务器 app.listen(PORT, () => { console.log(`Puppeteer服务器运行在 http://localhost:${PORT}`); console.log('API端点:'); console.log(' POST /api/generate-pdf - 生成PDF'); console.log(' POST /api/generate-images - 生成图片'); console.log(' GET /api/health - 健康检查'); }); module.exports = app;