Spaces:
Sleeping
Sleeping
| 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; |