Spaces:
Paused
Paused
| import * as http from 'http'; | |
| import * as url from 'url'; | |
| import * as path from 'path'; | |
| import { chromium } from 'playwright'; | |
| import { locks } from 'web-locks'; | |
| import crypto from 'crypto'; | |
| import fs from 'fs'; | |
| import { setTimeout } from 'timers/promises'; | |
| import { WebSocketServer } from 'ws'; | |
| // npm i playwright | |
| // npm i web-locks | |
| // npm i ws | |
| // nohup cloudflared tunnel --url http://localhost:8080 --no-autoupdate & (or setsid) | |
| // setsid node playwright.mjs (nohup doesnt work) | |
| // curl 'https://gowah44030-playwright.hf.space/.. | |
| // https://gowah44030-playwright.hf.space/screenshot?js=1&url=https://www.investing.com | |
| globalThis.state = Object.assign(globalThis.state||{}, { | |
| browser: null, | |
| context: null, | |
| }); | |
| async function text_from_url(url, cookie) { | |
| let { browser, context } = globalThis.state; | |
| if (!browser) { | |
| await locks.request('playwright_browser', async lock => { | |
| browser = globalThis.state.browser = await chromium.launch(); | |
| context = globalThis.state.context = await browser.newContext({ | |
| javaScriptEnabled: false | |
| }/*devices['iPhone 11']*/); | |
| }); | |
| } | |
| const page = await context.newPage(); | |
| if (cookie) { | |
| if (cookie.endsWith(';')) cookie = cookie.slice(0, -1); | |
| let cookies = cookie.split(';'); | |
| cookies = cookies.map(it=>{ | |
| let [name, value] = it.split('='); | |
| return {name: name.trim(), value: value.trim(), url}; | |
| }); | |
| context.addCookies(cookies); | |
| } | |
| await context.route("**/*.{png,jpg,jpeg,css,js}", route => route.abort()); | |
| await page.goto(url); | |
| let text; | |
| let i = 0; | |
| while (true) { | |
| let new_text = await page.evaluate(() => document.body.innerText); | |
| if (i > 5 || new_text?.length > 200) { | |
| text = new_text; | |
| break; | |
| } | |
| i++; | |
| await new Promise(resolve=>setTimeout(resolve, 1000)); | |
| } | |
| await page.close(); | |
| //await context.close(); | |
| // await browser.close(); | |
| return text; | |
| } | |
| async function screenshot(url, cookie, js_enabled) { | |
| let { browser, context } = globalThis.state; | |
| if (!browser) { | |
| console.log('launch browser'); | |
| await locks.request('playwright_browser', async lock => { | |
| browser = globalThis.state.browser = await chromium.launch(); | |
| context = globalThis.state.context = await browser.newContext({ | |
| javaScriptEnabled: js_enabled | |
| }/*devices['iPhone 11']*/); | |
| }); | |
| } | |
| const page = await context.newPage(); | |
| if (cookie) { | |
| if (cookie.endsWith(';')) cookie = cookie.slice(0, -1); | |
| let cookies = cookie.split(';'); | |
| cookies = cookies.map(it=>{ | |
| let [name, value] = it.split('='); | |
| return {name: name.trim(), value: value.trim(), url}; | |
| }); | |
| context.addCookies(cookies); | |
| } | |
| //await context.route("**/*.{png,jpg,jpeg,css,js}", route => route.abort()); | |
| await page.goto(url); | |
| await setTimeout(2000); | |
| // let id = crypto.randomUUID(); | |
| // let path = `/code/${id}.png`; | |
| // await page.screenshot({ path, fullPage: true }); | |
| const buffer = await page.screenshot({fullPage: true}); | |
| await page.close(); | |
| //await context.close(); | |
| // await browser.close(); | |
| return buffer; | |
| } | |
| async function evaluate(url, code) { | |
| let { browser, context } = globalThis.state; | |
| if (!browser) { | |
| console.log('launch browser'); | |
| await locks.request('playwright_browser', async lock => { | |
| browser = globalThis.state.browser = await chromium.launch({ | |
| args: ['--disable-web-security'] //https://github.com/microsoft/playwright/issues/17631 | |
| }); | |
| context = globalThis.state.context = await browser.newContext({ | |
| javaScriptEnabled: true, | |
| }/*devices['iPhone 11']*/); | |
| }); | |
| } | |
| const page = await context.newPage(); | |
| page.on('console', async (msg) => {console.log(msg);}); | |
| //await context.route("**/*.{png,jpg,jpeg,css,js}", route => route.abort()); | |
| await page.goto(url); | |
| let result = await page.evaluate(code); | |
| await page.close(); | |
| return result; | |
| } | |
| const server = http.createServer(async function(req, res) { | |
| const {query, pathname} = url.parse(req.url, true); | |
| console.log(req.url); | |
| res.setHeader('Access-Control-Allow-Origin', '*') | |
| let _url = query.url; | |
| let cookie = query.cookie; | |
| let js_enabled = query.js; | |
| try { | |
| if (pathname == '/text') { | |
| let text = await text_from_url(_url, cookie); | |
| res.end(text); | |
| } else if (pathname == '/screenshot') { | |
| let buffer = await screenshot(_url, cookie, js_enabled); | |
| res.writeHead(200,{'Content-type':'image/png'}); | |
| res.end(buffer); | |
| // const readStream = fs.createReadStream(path); | |
| // res.writeHead(200,{'Content-type':'image/png'}); | |
| // readStream.pipe(res); | |
| // res.end(); | |
| } else if (pathname == '/evaluate') { | |
| const buffers = []; | |
| for await (const chunk of req) { | |
| buffers.push(chunk); | |
| } | |
| const data = Buffer.concat(buffers).toString(); | |
| const {url, code} = JSON.parse(data); | |
| try { | |
| let result = await evaluate(url, code); | |
| res.end(''+result); | |
| } catch (e) { | |
| res.end(e.toString()); | |
| } | |
| } else { | |
| res.end('hello'); | |
| } | |
| } catch (e) { | |
| res.end(e.toString()); | |
| } | |
| }); | |
| const wss = new WebSocketServer({ server }); | |
| wss.on('connection', function connection(ws, req) { | |
| console.log('wss.connection', req.url, req.headers); | |
| ws.isAlive = true; | |
| ws.on('pong', function heartbeat(){ //'Pong messages are automatically sent in response to ping messages as required by the spec.' | |
| //console.log('pong'); | |
| this.isAlive = true; | |
| }); | |
| ws.on('message', function message(data) { | |
| console.log('received: %s', data); | |
| }); | |
| ws.addEventListener('close', function close() { | |
| console.log('ws.close'); | |
| }); | |
| //ws.send('something'); | |
| }); | |
| // https://github.com/websockets/ws | |
| const interval = setInterval(function ping() { | |
| wss.clients.forEach(function each(ws) { | |
| if (ws.isAlive === false) return ws.terminate(); | |
| ws.isAlive = false; | |
| ws.ping(); | |
| ws.send('ping');//why: used by client to detect dead connection (because browser cant access ping/pong frames...(?)) | |
| }); | |
| }, 30000); | |
| server.listen(7860); | |