const express = require('express'); const path = require('path'); const fs = require('fs'); const puppeteer = require('puppeteer-core'); 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' }); try { let response; 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 }); } const finalUrl = page.url(); console.log('Page URL after redirect (if any):', finalUrl); const content = await page.content(); return content; } catch (error) { console.error('Error scraping page:', error); return `Error: ${error.message}`; } finally { await browser.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.send(pageContent); } catch (error) { res.status(500).send('Failed to scrape the page'); } }); async function captureGDrivePayloadAndCookie(url, opts = {}) { const { chromePath = '/opt/google/chrome/chrome', headless = 'new', buttonSelector = '#gdriveButton', fallbackSelector = '.google-download', waitForButtonMs = 60000, waitForPostMs = 20000, } = 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', '--no-zygote', ], }); 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: () => false }); Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3] }); Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] }); window.chrome = { runtime: {} }; }); await page.setRequestInterception(true); page.on('request', (req) => { const rt = req.resourceType(); if (['image', 'stylesheet', 'font', 'media'].includes(rt)) req.abort(); else req.continue(); }); await page.goto(url, { waitUntil: 'load', timeout: 60000 }); await page.waitForFunction(() => document.readyState === 'complete', { timeout: 30000 }).catch(() => { /* ignore */ }); let foundSelector = null; try { console.log('⏳ Waiting for primary selector:', buttonSelector); await page.waitForSelector(buttonSelector, { visible: true, timeout: waitForButtonMs }); foundSelector = buttonSelector; } catch (errPrimary) { console.log('primary selector not found, trying fallback:', fallbackSelector); try { await page.waitForSelector(fallbackSelector, { visible: true, timeout: Math.max(5000, waitForButtonMs / 4) }); foundSelector = fallbackSelector; } catch (errFallback) { throw new Error(`Button not found using selectors "${buttonSelector}" or "${fallbackSelector}"`); } } console.log('✅ Found button selector:', foundSelector); const postRequestPromise = page.waitForRequest( (req) => { try { if (req.method() !== 'POST') return false; const body = req.postData() || ''; if (body.includes('"gdrive"') || body.includes('gdrive') || /token\s*[:=]/i.test(body)) return true; } catch (e) { } return false; }, { timeout: waitForPostMs } ); await page.evaluate((sel) => { const el = document.querySelector(sel); if (el) { el.scrollIntoView({ behavior: 'auto', block: 'center', inline: 'center' }); } }, foundSelector); await page.click(foundSelector); let matchedRequest; try { matchedRequest = await postRequestPromise; } catch (err) { // no post captured throw new Error('Timed out waiting for POST request after clicking the button'); } const headers = matchedRequest.headers() || {}; const cookieHeader = headers.cookie || null; const rawBody = matchedRequest.postData() || ''; let parsedPayload = null; try { parsedPayload = JSON.parse(rawBody); } catch (e) { try { const params = new URLSearchParams(rawBody); if ([...params].length > 0) { parsedPayload = {}; for (const [k, v] of params.entries()) parsedPayload[k] = v; } else { parsedPayload = { raw: rawBody }; } } catch (e2) { parsedPayload = { raw: rawBody }; } } await page.close(); await browser.close(); return { success: true, cookies: cookieHeader, token: parsedPayload.token, requestUrl: matchedRequest.url(), }; } catch (err) { console.error('captureGDrivePayloadAndCookie error:', err.message || 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 captureGDrivePayloadAndCookie(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 res.json({ cookie: result.cookieHeader, payload: result.payload, requestUrl: result.requestUrl, }); } catch (err) { console.error('/api/bypass error:', err); return res.status(500).json({ error: 'Internal error' }); } }); app.get('/api/cinex', async (req, res) => { const { url } = req.query; if (!url) { return res.status(400).json({ error: 'URL is required' }); } const result = await captureGDrivePayloadAndCookie(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 res.json({ cookie: result.cookieHeader, payload: result.payload, requestUrl: result.requestUrl, }); }); // 健康检查端点 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;