<div class="whitespace-pre-wrap">Automasi max.ai converter PDF to HTML

Tech : Node.js (common), Socket.io, Puppeteer

target : https://www.maxai.co/pdf-tools/pdf-to-html/
selector : 
1. input = &lt;input type="file" title="" accept="application/pdf" multiple="" class="css-1gcv6dl"&gt;
2. finish button = &lt;button class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeLarge MuiButton-containedSizeLarge MuiButton-colorPrimary MuiButton-disableElevation MuiButton-fullWidth MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeLarge MuiButton-containedSizeLarge MuiButton-colorPrimary MuiButton-disableElevation MuiButton-fullWidth css-y450x3" tabindex="0" type="button"&gt;&lt;p class="MuiTypography-root MuiTypography-body1 css-1ncveae"&gt;Finish&lt;/p&gt;&lt;svg class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium css-10lx4aw" focusable="false" aria-hidden="true" viewBox="0 0 24 24" data-testid="EastIcon"&gt;&lt;path d="m15 5-1.41 1.41L18.17 11H2v2h16.17l-4.59 4.59L15 19l7-7z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;span class="MuiTouchRipple-root css-w0pj6f"&gt;&lt;/span&gt;&lt;/button&gt;
3. download button = &lt;button class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButton-colorPrimary MuiButton-disableElevation MuiButton-fullWidth MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButton-colorPrimary MuiButton-disableElevation MuiButton-fullWidth css-1kwztf9" tabindex="0" type="button"&gt;&lt;div class="MuiBox-root css-axw7ok"&gt;&lt;svg class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium css-wbvhrc" focusable="false" aria-hidden="true" viewBox="0 0 24 24" data-testid="FileDownloadOutlinedIcon"&gt;&lt;path d="M18 15v3H6v-3H4v3c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2v-3zm-1-4-1.41-1.41L13 12.17V4h-2v8.17L8.41 9.59 7 11l5 5z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;p class="MuiTypography-root MuiTypography-body1 css-ww1t3z"&gt;Download&lt;/p&gt;&lt;/div&gt;&lt;span class="MuiTouchRipple-root css-w0pj6f"&gt;&lt;/span&gt;&lt;/button&gt;
4. new upload = &lt;button class="MuiButtonBase-root MuiButton-root MuiButton-outlined MuiButton-outlinedPrimary MuiButton-sizeMedium MuiButton-outlinedSizeMedium MuiButton-colorPrimary MuiButton-disableElevation MuiButton-fullWidth MuiButton-root MuiButton-outlined MuiButton-outlinedPrimary MuiButton-sizeMedium MuiButton-outlinedSizeMedium MuiButton-colorPrimary MuiButton-disableElevation MuiButton-fullWidth css-bty1zy" tabindex="0" type="button"&gt;&lt;div class="MuiBox-root css-axw7ok"&gt;&lt;svg class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium css-wbvhrc" focusable="false" aria-hidden="true" viewBox="0 0 24 24" data-testid="RestartAltOutlinedIcon"&gt;&lt;path d="M6 13c0-1.65.67-3.15 1.76-4.24L6.34 7.34C4.9 8.79 4 10.79 4 13c0 4.08 3.05 7.44 7 7.93v-2.02c-2.83-.48-5-2.94-5-5.91m14 0c0-4.42-3.58-8-8-8-.06 0-.12.01-.18.01l1.09-1.09L11.5 2.5 8 6l3.5 3.5 1.41-1.41-1.08-1.08c.06 0 .12-.01.17-.01 3.31 0 6 2.69 6 6 0 2.97-2.17 5.43-5 5.91v2.02c3.95-.49 7-3.85 7-7.93"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;p class="MuiTypography-root MuiTypography-body1 css-ww1t3z"&gt;Start over&lt;/p&gt;&lt;/div&gt;&lt;span class="MuiTouchRipple-root css-w0pj6f"&gt;&lt;/span&gt;&lt;/button&gt;

Puppeteernya buatkan safe/stealth

berikut existing project structurenya:

pacakge.json
{
    "name": "pdftohtml_cvgen",
    "version": "1.0.0",
    "description": "",
    "homepage": "https://github.com/refkinscallv/pdftohtml_cvgen#readme",
    "bugs": {
        "url": "https://github.com/refkinscallv/pdftohtml_cvgen/issues"
    },
    "repository": {
        "type": "git",
        "url": "git+https://github.com/refkinscallv/pdftohtml_cvgen.git"
    },
    "license": "MIT",
    "author": "Refkinscallv &lt;refkinscallv@gmail.com&gt;",
    "type": "commonjs",
    "main": "src/index.js",
    "scripts": {
        "test": "echo \"Error: no test specified\" &amp;&amp; exit 1",
        "start": "node src/index.js",
        "dev": "nodemon",
        "format": "prettier --ignore-path .prettierignore --write src/**/*.js"
    },
    "dependencies": {
        "@refkinscallv/express-routing": "^1.2.1",
        "dotenv": "^17.2.1",
        "puppeteer-extra": "^3.3.6",
        "puppeteer-extra-plugin-stealth": "^2.11.2",
        "socket.io": "^4.8.1"
    },
    "devDependencies": {
        "nodemon": "^3.1.10",
        "prettier": "^3.6.2"
    }
}

nodemon.json
{
    "watch": ["src"],
    "ext": "js",
    "exec": "node ./src/index.js"
}

src/index.js
const Boot = require('./boot')

Boot.run({
    server: {
        url: 'http://localhost:3456',
        port: 3456,
    },
})

src/boot.js
const Boot = require('./boot')

Boot.run({
    server: {
        url: 'http://localhost:3456',
        port: 3456,
    },
})

src/server.js
const http = require('http')

module.exports = class Server {
    static server = null

    static init(data) {
        const { port, url } = data

        this.server = http.createServer((req, res) =&gt; {
            res.writeHead(200, { 'Content-Type': 'text/html' })
            res.end('Server is running\n')
        })

        this.server.listen(port, () =&gt; {
            console.log(<code>[SERVER] Application already running : ${url || </code>http://localhost:${port}<code>}</code>)
        })

        this.server.on('error', (err) =&gt; {
            console.error('[SERVER] Error:', err.message)
        })
    }
}

src/socket.js
const { Server: SocketIO } = require('socket.io')
const Server = require('./server')

module.exports = class Socket {
    static io = null

    static init() {
        this.io = new SocketIO(Server.server, {
            cors: { origin: '*' }
        })

        this.io.on('connection', (socket) =&gt; {
            console.log('[SOCKET] Client connected:', socket.id)

            socket.on('disconnect', () =&gt; {
                console.log('[SOCKET] Client disconnected:', socket.id)
            })
        })
    }
}

tinggal puppeteer yang belum</div>