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