File size: 6,435 Bytes
98e9a75
 
 
 
 
ae0a0ca
 
0e7a465
9609abc
 
98e9a75
 
 
9609abc
ae0a0ca
98e9a75
 
 
ae0a0ca
9609abc
ae0a0ca
98e9a75
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96ef720
ae0a0ca
 
 
 
 
96ef720
ae0a0ca
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
06875d1
 
 
ae0a0ca
ab2fb2f
ae0a0ca
 
 
 
 
06875d1
ae0a0ca
 
7bc4d92
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98e9a75
 
 
 
 
 
 
 
13fe225
98e9a75
 
ae0a0ca
 
 
 
 
13fe225
ae0a0ca
 
06875d1
 
 
 
 
 
7bc4d92
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ae0a0ca
 
 
98e9a75
 
 
 
 
9609abc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98e9a75
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
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);