Spaces:
Sleeping
Sleeping
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) { | |
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) { | |
await locks.request('playwright_browser', async lock => { | |
browser = globalThis.state.browser = await chromium.launch(); | |
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); | |
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); | |