|
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 |
|
}; |
|
|
|
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; |
|
} |
|
|
|
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()); |
|
|
|
|
|
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}`); |
|
}); |
|
} |
|
|
|
|
|
if (cluster.isPrimary) { |
|
console.log(`Primary ${process.pid} is running`); |
|
|
|
|
|
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(); |
|
}); |
|
} else { |
|
startServer().catch(console.error); |
|
} |
|
|
|
process.on('SIGINT', async () => { |
|
await bratService.close(); |
|
process.exit(0); |
|
}); |