import express from 'express'; import { chromium } from 'playwright'; import cors from 'cors'; import dotenv from 'dotenv'; import os from 'os'; import sharp from 'sharp'; import cluster from 'cluster'; import { cpus } from 'os'; dotenv.config(); const config = { maxTextLength: 100, viewport: { width: 1920, height: 1080 }, userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', concurrentBrowsers: cpus().length // Gunakan jumlah core CPU }; class BratGeneratorService { constructor() { this.browsers = []; this.pages = []; this.isInitialized = false; } async initialize() { if (this.isInitialized) return; const launchPromises = Array.from({ length: config.concurrentBrowsers }, async () => { const browser = await chromium.launch({ headless: true, args: [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage' ] }); const context = await browser.newContext({ viewport: config.viewport, userAgent: config.userAgent }); await context.route('**/*', (route) => { const url = route.request().url(); if (url.endsWith('.png') || url.endsWith('.jpg') || url.includes('google-analytics')) { return route.abort(); } route.continue(); }); const page = await context.newPage(); await page.goto('https://www.bratgenerator.com/', { waitUntil: 'domcontentloaded', timeout: 5000 }); try { await page.click('#onetrust-accept-btn-handler', { timeout: 2000 }); } catch { } await page.evaluate(() => setupTheme('white')); this.browsers.push(browser); this.pages.push(page); }); await Promise.all(launchPromises); this.isInitialized = true; } async generateBrat(text, pageIndex = 0) { if (!this.pages[pageIndex]) { pageIndex = 0; // Reset jika index tidak valid } const page = this.pages[pageIndex]; await page.fill('#textInput', text); const overlay = page.locator('#textOverlay'); const pngBuffer = await overlay.screenshot({ timeout: 3000, type: 'png' }); return sharp(pngBuffer) .webp({ quality: 80 }) .toBuffer(); } async close() { const closePromises = this.browsers.map(browser => browser.close()); await Promise.all(closePromises); this.browsers = []; this.pages = []; this.isInitialized = false; } } const bratService = new BratGeneratorService(); async function startServer() { const app = express(); app.use(express.json()); app.use(cors()); // Middleware untuk membatasi panjang teks const validateTextLength = (req, res, next) => { const { q } = req.query; if (!q || q.length > config.maxTextLength) { return res.status(400).json({ status: false, message: `Teks harus diisi dan maksimal ${config.maxTextLength} karakter` }); } next(); }; app.get('*', validateTextLength, async (req, res) => { try { const { q } = req.query; const pageIndex = Math.floor(Math.random() * config.concurrentBrowsers); const imageBuffer = await bratService.generateBrat(q, pageIndex); res.set('Content-Type', 'image/webp'); res.send(imageBuffer); } catch (error) { console.error(error); res.status(500).json({ status: false, message: 'Error generating image', error: process.env.NODE_ENV === 'development' ? error.message : undefined }); } }); const PORT = process.env.PORT || 7860; await bratService.initialize(); app.listen(PORT, () => { console.log(`Server running on port ${PORT}`); console.log(`Worker PID: ${process.pid}`); }); } // Gunakan cluster untuk scaling if (cluster.isPrimary) { console.log(`Primary ${process.pid} is running`); // Fork workers. for (let i = 0; i < cpus().length; i++) { cluster.fork(); } cluster.on('exit', (worker, code, signal) => { console.log(`worker ${worker.process.pid} died`); cluster.fork(); // Restart worker }); } else { startServer().catch(console.error); } process.on('SIGINT', async () => { await bratService.close(); process.exit(0); });