Spaces:
Running
Running
| const puppeteer = require('puppeteer'); | |
| const axios = require('axios'); | |
| const Jimp = require('jimp'); | |
| const FormData = require('form-data'); | |
| const UPLOAD_API = 'https://cdnme.idnet.my.id/upload'; | |
| const devicesMap = { | |
| iphone17: { | |
| userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1', | |
| viewport: { | |
| width: 402, | |
| height: 874, | |
| deviceScaleFactor: 3, | |
| isMobile: true, | |
| hasTouch: true | |
| } | |
| } | |
| }; | |
| async function generateBrat(text) { | |
| const device = devicesMap.iphone17; | |
| const browser = await puppeteer.launch({ | |
| headless: 'new', | |
| executablePath: process.env.PUPPETEER_EXECUTABLE_PATH || '/usr/bin/chromium', | |
| args: [ | |
| '--no-sandbox', | |
| '--disable-setuid-sandbox', | |
| '--disable-dev-shm-usage', | |
| '--disable-accelerated-2d-canvas', | |
| '--disable-gpu', | |
| '--font-render-hinting=none', | |
| '--force-color-profile=srgb', | |
| '--disable-web-security' | |
| ] | |
| }); | |
| try { | |
| const page = await browser.newPage(); | |
| await page.setViewport(device.viewport); | |
| await page.setUserAgent(device.userAgent); | |
| await page.evaluateOnNewDocument(() => { | |
| Object.defineProperty(navigator, 'platform', { | |
| get: () => 'iPhone' | |
| }); | |
| }); | |
| await page.goto('https://www.bratgenerator.com/', { | |
| waitUntil: 'networkidle2', | |
| timeout: 60000 | |
| }); | |
| await page.addStyleTag({ | |
| content: ` | |
| * { | |
| font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", sans-serif !important; | |
| } | |
| body, input, textarea, canvas { | |
| font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", sans-serif !important; | |
| } | |
| ` | |
| }); | |
| await page.waitForSelector('#textInput', { timeout: 30000 }); | |
| const whiteButton = await page.$('#toggleButtonWhite'); | |
| if (whiteButton) { | |
| await page.click('#toggleButtonWhite'); | |
| await new Promise(resolve => setTimeout(resolve, 500)); | |
| } | |
| await page.evaluate((txt) => { | |
| const input = document.getElementById('textInput'); | |
| input.value = txt; | |
| input.dispatchEvent(new Event('input', { bubbles: true })); | |
| input.dispatchEvent(new Event('change', { bubbles: true })); | |
| }, text); | |
| await new Promise(resolve => setTimeout(resolve, 2000)); | |
| const canvas = await page.$('canvas'); | |
| if (canvas) { | |
| const screenshot = await canvas.screenshot({ | |
| type: 'png', | |
| encoding: 'base64' | |
| }); | |
| await browser.close(); | |
| return screenshot; | |
| } | |
| const screenshot = await page.screenshot({ | |
| type: 'png', | |
| encoding: 'base64', | |
| fullPage: false | |
| }); | |
| await browser.close(); | |
| return screenshot; | |
| } catch (error) { | |
| await browser.close(); | |
| throw error; | |
| } | |
| } | |
| async function processImage(base64Data) { | |
| const imageBuffer = Buffer.from(base64Data, 'base64'); | |
| const image = await Jimp.read(imageBuffer); | |
| const width = image.getWidth(); | |
| const height = image.getHeight(); | |
| const size = Math.min(width, height); | |
| const x = Math.floor((width - size) / 2); | |
| const y = Math.floor((height - size) / 2); | |
| image.crop(x, y, size, size); | |
| image.resize(1080, 1080); | |
| return await image.getBufferAsync(Jimp.MIME_PNG); | |
| } | |
| async function uploadImage(imageBuffer) { | |
| const form = new FormData(); | |
| form.append('file', imageBuffer, { | |
| filename: `brat-${Date.now()}.png`, | |
| contentType: 'image/png' | |
| }); | |
| const response = await axios.post(UPLOAD_API, form, { | |
| headers: form.getHeaders(), | |
| timeout: 30000 | |
| }); | |
| if (!response.data.success) { | |
| throw new Error('Failed to upload image'); | |
| } | |
| return response.data.url; | |
| } | |
| const handler = async (req, res) => { | |
| try { | |
| const { text } = req.query; | |
| if (!text) { | |
| return res.status(400).json({ | |
| success: false, | |
| error: 'Missing required parameter: text' | |
| }); | |
| } | |
| const base64Screenshot = await generateBrat(text); | |
| const processedImage = await processImage(base64Screenshot); | |
| const imageUrl = await uploadImage(processedImage); | |
| return res.json({ | |
| author: "Herza", | |
| success: true, | |
| data: { | |
| text: text, | |
| url: imageUrl, | |
| size: "1080x1080", | |
| } | |
| }); | |
| } catch (error) { | |
| console.error('Error:', error); | |
| return res.status(500).json({ | |
| success: false, | |
| error: error.message, | |
| timestamp: new Date().toISOString() | |
| }); | |
| } | |
| }; | |
| module.exports = { | |
| name: 'Brat Generator', | |
| description: 'Generate brat style images with custom text and Apple emoji', | |
| type: 'GET', | |
| routes: ['api/maker/brat'], | |
| tags: ['maker'], | |
| parameters: ['text'], | |
| limit: null, | |
| enabled: true, | |
| main: ['Maker'], | |
| handler | |
| }; |