|
import express from 'express'; |
|
import { chromium } from 'playwright'; |
|
import cors from 'cors'; |
|
import dotenv from 'dotenv'; |
|
import os from 'os'; |
|
import fetch from 'node-fetch'; |
|
import sharp from 'sharp'; |
|
|
|
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' |
|
}; |
|
|
|
let browser, page; |
|
|
|
const utils = { |
|
async initialize() { |
|
if (!browser) { |
|
browser = await chromium.launch({ headless: true }); |
|
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(); |
|
}); |
|
|
|
page = await context.newPage(); |
|
await page.goto('https://www.bratgenerator.com/', { waitUntil: 'domcontentloaded', timeout: 10000 }); |
|
|
|
try { |
|
await page.click('#onetrust-accept-btn-handler', { timeout: 2000 }); |
|
} catch { } |
|
|
|
await page.evaluate(() => setupTheme('white')); |
|
} |
|
}, |
|
|
|
async generateBrat(text) { |
|
await page.fill('#textInput', text); |
|
const overlay = page.locator('#textOverlay'); |
|
return overlay.screenshot({ timeout: 3000 }); |
|
}, |
|
|
|
async close() { |
|
if (browser) await browser.close(); |
|
} |
|
}; |
|
|
|
const app = express(); |
|
app.use(express.json()); |
|
app.use(cors()); |
|
|
|
app.get('/brat', async (req, res) => { |
|
try { |
|
const { q } = req.query; |
|
if (!q) { |
|
return res.json({ |
|
name: 'HD Bart Generator API', |
|
message: 'Parameter q di perlukan', |
|
version: '2.1.0', |
|
runtime: { |
|
os: os.type(), |
|
platform: os.platform(), |
|
architecture: os.arch(), |
|
cpuCount: os.cpus().length, |
|
uptime: `${os.uptime()} seconds`, |
|
memoryUsage: `${Math.round((os.totalmem() - os.freemem()) / 1024 / 1024)} MB used of ${Math.round(os.totalmem() / 1024 / 1024)} MB` |
|
} |
|
}); |
|
} |
|
const imageBuffer = await utils.generateBrat(q); |
|
res.set('Content-Type', 'image/png'); |
|
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 font = 'BlueArchive'; |
|
|
|
const crossBuffer = Buffer.from('<svg width="480" height="200" version="1.1"><polygon points="252,0 159,190 165,190 287,0" style="fill:#fff" /></svg>'); |
|
const haloBuffer = Buffer.from('<svg width="480" height="200" version="1.1"><polygon points="252,0 159,190 165,190 287,0" style="fill:#fff" /></svg>'); |
|
|
|
const splitText = (text) => { |
|
if (Array.isArray(text)) return text; |
|
if (text.includes(' ')) { |
|
return text.split(' ').filter(i => i.trim()); |
|
} else if (text.match(/^[A-Z][a-z]*[A-Z][a-z]*$/)) { |
|
return text.replace(/[A-Z]/g, t => ' ' + t).trim().split(' '); |
|
} else { |
|
const h = Math.floor(text.length / 2); |
|
return [text.substring(0, h), text.substring(h)]; |
|
} |
|
}; |
|
|
|
const baLogo = async (text, left = 0) => { |
|
const [head, tail] = splitText(text); |
|
if (!head || !tail) throw new Error('Invalid input text'); |
|
|
|
let width = 32, height = 260; |
|
const top = 208; |
|
const matrix = [[1, -0.35], [0, 1]]; |
|
|
|
const comps = []; |
|
|
|
const headPart = sharp({ |
|
text: { |
|
font, |
|
text: `<span color="#208ef7" size="144pt">${head}</span>`, |
|
dpi: 72, |
|
rgba: true, |
|
} |
|
}).affine(matrix, { |
|
background: '#fff0', |
|
interpolator: sharp.interpolators.nohalo |
|
}).png(); |
|
const headMeta = await headPart.metadata(); |
|
|
|
const w = width + headMeta.width - 162; |
|
const dl = w < 0 ? 0 : w + left; |
|
|
|
const tailPart = sharp({ |
|
text: { |
|
font, |
|
text: `<span color="#208ef7" size="144pt">${head}<span color="#2b2b2b" size="144pt">${tail}</span></span>`, |
|
dpi: 72, |
|
rgba: true, |
|
} |
|
}).affine(matrix, { |
|
background: '#fff0', |
|
interpolator: sharp.interpolators.nohalo |
|
}).png(); |
|
const tailMeta = await tailPart.metadata(); |
|
|
|
comps.push({ |
|
input: await tailPart.toBuffer(), |
|
left: width, |
|
top, |
|
}); |
|
|
|
comps.push({ |
|
input: crossBuffer, |
|
left: dl + 4, |
|
top: 4, |
|
}); |
|
|
|
width += (tailMeta.width < 256 ? 256 : tailMeta.width ) + 64; |
|
height += 144; |
|
if (width < 500) width = 500; |
|
|
|
return sharp({ |
|
create: { |
|
width, height, |
|
channels: 4, |
|
background: '#fff', |
|
}, |
|
}).composite(comps).png(); |
|
}; |
|
|
|
app.get('/balogo', async (req, res) => { |
|
try { |
|
const { text, text2 } = req.query; |
|
if (!text) return res.status(400).send('Text is required'); |
|
|
|
const logoImage = await baLogo(text); |
|
res.set('Content-Type', 'image/png'); |
|
res.send(await logoImage.toBuffer()); |
|
} catch (error) { |
|
res.status(500).send('Error generating logo: ' + error.message); |
|
} |
|
}); |
|
|
|
async function baLogov2(textL = "蔚蓝", textR = "档案", x = "-15", y = "0", tp = false) { |
|
const browser = await chromium.launch({ headless: true }); |
|
const context = await browser.newContext({ |
|
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' |
|
}); |
|
const page = await context.newPage(); |
|
await page.goto('https://symbolon.pages.dev/'); |
|
await page.waitForSelector('#canvas'); |
|
await page.fill('#textL', textL); |
|
await page.fill('#textR', textR); |
|
await page.fill('#graphX', x); |
|
await page.fill('#graphY', y); |
|
await page.waitForTimeout(500); |
|
const imageBuffer = await page.$eval('#canvas', (canvas) => { |
|
return canvas.toDataURL('image/png').split(',')[1]; |
|
}); |
|
const buffer = Buffer.from(imageBuffer, 'base64'); |
|
await browser.close(); |
|
return buffer; |
|
} |
|
|
|
app.get('/balogo/v2', async (req, res) => { |
|
try { |
|
const { textL = "蔚蓝", textR = "档案", x = "-15", y = "0", tp = "false" } = req.query; |
|
const tpBool = tp === "true"; |
|
const imageBuffer = await baLogov2(textL, textR, x, y, tpBool); |
|
res.setHeader('Content-Type', 'image/png'); |
|
res.send(imageBuffer); |
|
} catch (error) { |
|
console.error(error); |
|
res.status(500).json({ error: "Error generating logo" }); |
|
} |
|
}); |
|
|
|
async function baLogov3(textL = "蔚蓝", textR = "档案") { |
|
const browser = await chromium.launch({ headless: true }); |
|
const context = await browser.newContext({ |
|
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' |
|
}); |
|
const page = await context.newPage(); |
|
await page.goto('https://appleneko2001-bluearchive-logo.vercel.app/'); |
|
|
|
try { |
|
await page.waitForSelector('#canvas', { timeout: 30000 }); |
|
} catch (err) { |
|
console.error('Canvas tidak ditemukan:', err); |
|
} |
|
|
|
try { |
|
await page.waitForSelector('#textL', { state: 'visible', timeout: 10000 }); |
|
await page.fill('#textL', textL); |
|
} catch (err) { |
|
console.error('Elemen #textL gagal diisi:', err); |
|
} |
|
|
|
try { |
|
await page.waitForSelector('#textR', { state: 'visible', timeout: 10000 }); |
|
await page.fill('#textR', textR); |
|
} catch (err) { |
|
console.error('Elemen #textR gagal diisi:', err); |
|
} |
|
|
|
await page.waitForTimeout(2000); |
|
|
|
const imageBuffer = await page.$eval('#canvas', (canvas) => { |
|
return canvas.toDataURL('image/png').split(',')[1]; |
|
}); |
|
|
|
const buffer = Buffer.from(imageBuffer, 'base64'); |
|
|
|
await browser.close(); |
|
return buffer; |
|
} |
|
|
|
app.get('/balogo/v3', async (req, res) => { |
|
try { |
|
const { textL = "蔚蓝", textR = "档案" } = req.query; |
|
const imageBuffer = await baLogov3(textL, textR); |
|
res.setHeader('Content-Type', 'image/png'); |
|
res.send(imageBuffer); |
|
} catch (error) { |
|
console.error(error); |
|
res.status(500).json({ error: "Error generating logo" }); |
|
} |
|
}); |
|
|
|
async function baLogov4(textL = "蔚蓝", textR = "档案") { |
|
const browser = await chromium.launch({ headless: true }); |
|
const context = await browser.newContext({ |
|
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' |
|
}); |
|
const page = await context.newPage(); |
|
await page.goto('https://ba.api.hli.icu/'); |
|
|
|
try { |
|
await page.waitForSelector('#canvas', { timeout: 30000 }); |
|
} catch (err) { |
|
console.error('Canvas tidak ditemukan:', err); |
|
} |
|
|
|
try { |
|
await page.waitForSelector('#textL', { state: 'visible', timeout: 10000 }); |
|
await page.fill('#textL', textL); |
|
} catch (err) { |
|
console.error('Elemen #textL gagal diisi:', err); |
|
} |
|
|
|
try { |
|
await page.waitForSelector('#textR', { state: 'visible', timeout: 10000 }); |
|
await page.fill('#textR', textR); |
|
} catch (err) { |
|
console.error('Elemen #textR gagal diisi:', err); |
|
} |
|
|
|
await page.waitForTimeout(500); |
|
|
|
const imageBuffer = await page.$eval('#canvas', (canvas) => { |
|
return canvas.toDataURL('image/png').split(',')[1]; |
|
}); |
|
|
|
const buffer = Buffer.from(imageBuffer, 'base64'); |
|
|
|
await browser.close(); |
|
return buffer; |
|
} |
|
|
|
app.get('/balogo/v4', async (req, res) => { |
|
try { |
|
const { textL = "蔚蓝", textR = "档案" } = req.query; |
|
const imageBuffer = await baLogov4(textL, textR); |
|
res.setHeader('Content-Type', 'image/png'); |
|
res.send(imageBuffer); |
|
} catch (error) { |
|
console.error(error); |
|
res.status(500).json({ error: "Error generating logo" }); |
|
} |
|
}); |
|
|
|
app.get('/rayso', async (req, res) => { |
|
const { code, title = 'app.js', theme = 'supabase', language = '', darkMode = 'true', background = 'false' } = req.query; |
|
|
|
if (!code) |
|
return res.status(400).json({ error: 'Parameter "code" (Base64 encoded) diperlukan' }); |
|
|
|
try { |
|
const encodedCode = Buffer.from(code, 'base64'); |
|
const url = `https://www.ray.so/#code=${encodeURIComponent(encodedCode)}&title=${encodeURIComponent(title)}&theme=${theme}&language=${language}&background=${background}&darkMode=${darkMode}`; |
|
|
|
const browser = await chromium.launch({ headless: true }); |
|
const context = await browser.newContext({ |
|
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' |
|
}); |
|
const page = await context.newPage(); |
|
|
|
await page.goto(url); |
|
await page.waitForSelector('.Frame_frame__rcr69'); |
|
|
|
const elementHandle = await page.$('.Frame_frame__rcr69'); |
|
const screenshotBuffer = await elementHandle.screenshot({ fullPage: true }); |
|
|
|
await browser.close(); |
|
|
|
res.setHeader('Content-Type', 'image/png'); |
|
return res.status(200).end(screenshotBuffer); |
|
} catch (error) { |
|
return res.status(500).json({ error: 'Gagal mengambil screenshot', details: error.message }); |
|
} |
|
}); |
|
|
|
app.get('/carbon', async (req, res) => { |
|
const { code, theme } = req.query; |
|
|
|
if (!code) return res.status(400).json({ error: 'Parameter "code" diperlukan.' }); |
|
if (!theme) return res.status(400).json({ error: 'Parameter "theme" diperlukan.' }); |
|
|
|
try { |
|
const browser = await chromium.launch({ headless: true }); |
|
const context = await browser.newContext({ |
|
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', |
|
acceptDownloads: true |
|
}); |
|
const page = await context.newPage(); |
|
|
|
await page.goto('https://carbon.now.sh/'); |
|
await page.click('input[aria-labelledby="theme-dropdown"]'); |
|
await page.keyboard.type(theme); |
|
await page.keyboard.press('Enter'); |
|
await page.click('pre.CodeMirror-line span[role="presentation"]'); |
|
await page.keyboard.press('Control+A'); |
|
await page.keyboard.press('Backspace'); |
|
await page.keyboard.type(code); |
|
await page.waitForSelector('button[data-cy="quick-export-button"]'); |
|
const [download] = await Promise.all([ |
|
page.waitForEvent('download'), |
|
page.click('button[data-cy="quick-export-button"]') |
|
]); |
|
const downloadPath = await download.path(); |
|
|
|
res.setHeader('Content-Type', 'application/octet-stream'); |
|
return res.status(200).sendFile(downloadPath); |
|
} catch (error) { |
|
return res.status(500).json({ error: 'Gagal memproses permintaan.', details: error.message }); |
|
} finally { |
|
await browser.close(); |
|
} |
|
}); |
|
|
|
app.get('/recoded', async (req, res) => { |
|
const { code, colors, font, lang, bg } = req.query; |
|
|
|
if (!code) return res.status(400).json({ error: 'Parameter "code" diperlukan.' }); |
|
if (!colors) return res.status(400).json({ error: 'Parameter "colors" diperlukan.' }); |
|
if (!font) return res.status(400).json({ error: 'Parameter "font" diperlukan.' }); |
|
if (!lang) return res.status(400).json({ error: 'Parameter "lang" diperlukan.' }); |
|
if (!bg) return res.status(400).json({ error: 'Parameter "bg" diperlukan.' }); |
|
|
|
try { |
|
const browser = await chromium.launch({ headless: true }); |
|
const context = await browser.newContext({ |
|
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', |
|
acceptDownloads: true, |
|
}); |
|
const page = await context.newPage(); |
|
|
|
await page.goto('https://recoded.netlify.app/'); |
|
await page.fill('textarea.npm__react-simple-code-editor__textarea', code); |
|
|
|
await page.click('div.input-box[style*="120px"]:has-text("VS Code Dark")'); |
|
await page.keyboard.type(colors); |
|
await page.keyboard.press('Enter'); |
|
|
|
await page.click('div[data-testid="font-select"]'); |
|
await page.keyboard.type(font); |
|
await page.keyboard.press('Enter'); |
|
|
|
await page.click('div[data-testid="language-select"]'); |
|
await page.keyboard.type(lang); |
|
await page.keyboard.press('Enter'); |
|
|
|
await page.click('div[data-testid="background-color-select"]'); |
|
await page.keyboard.type(bg); |
|
await page.keyboard.press('Enter'); |
|
|
|
const [download] = await Promise.all([ |
|
page.waitForEvent('download'), |
|
page.click('button.button.export') |
|
]); |
|
const downloadPath = await download.path(); |
|
|
|
res.setHeader('Content-Type', 'image/png'); |
|
return res.status(200).send(downloadPath); |
|
} catch (error) { |
|
return res.status(500).json({ error: 'Gagal memproses permintaan.', details: error.message }); |
|
} finally { |
|
await browser.close(); |
|
} |
|
}); |
|
|
|
app.get('/chalkist', async (req, res) => { |
|
const { code, title } = req.query; |
|
|
|
if (!code) return res.status(400).json({ error: 'Parameter "code" diperlukan.' }); |
|
if (!title) return res.status(400).json({ error: 'Parameter "title" diperlukan.' }); |
|
|
|
let browser; |
|
try { |
|
browser = await chromium.launch({ headless: true }); |
|
const context = await browser.newContext({ |
|
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', |
|
}); |
|
const page = await context.newPage(); |
|
|
|
await page.goto('https://chalk.ist/'); |
|
|
|
|
|
const titleSelector = 'div[data-placeholder="Untitled..."]'; |
|
await page.focus(titleSelector); |
|
await page.keyboard.type(title); |
|
|
|
|
|
const codeSelector = 'textarea.editor.font-config'; |
|
await page.fill(codeSelector, code); |
|
|
|
|
|
const screenshotSelector = 'div[data-editor-frame]'; |
|
await page.waitForSelector(screenshotSelector); |
|
|
|
|
|
const element = await page.$(screenshotSelector); |
|
const screenshotBuffer = await element.screenshot(); |
|
|
|
res.setHeader('Content-Type', 'image/png'); |
|
res.status(200).send(screenshotBuffer); |
|
} catch (error) { |
|
console.error('Error:', error); |
|
res.status(500).json({ error: 'Gagal memproses permintaan.', details: error.message }); |
|
} finally { |
|
if (browser) await browser.close(); |
|
} |
|
}); |
|
|
|
app.get('/brat/v2', async (req, res) => { |
|
const { text } = req.query; |
|
|
|
if (!text) return res.status(400).json({ error: 'Parameter "text" diperlukan.' }); |
|
|
|
try { |
|
const browser = await chromium.launch({ headless: true }); |
|
const context = await browser.newContext({ |
|
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', |
|
acceptDownloads: true, |
|
}); |
|
|
|
const page = await context.newPage(); |
|
|
|
await page.goto('https://brat-generator.net/'); |
|
await page.fill('input[class="w-full p-3 text-base border border-gray-200 rounded-lg mb-5 focus:border-blue-500 focus:ring-1 focus:ring-blue-500 transition-colors"]', text); |
|
|
|
try { |
|
await page.waitForSelector('button[class="px-5 py-3 text-sm font-medium rounded-lg flex items-center justify-between flex-1 transition-all bg-white border-2 border-blue-500 text-blue-600"]', { visible: true }); |
|
await page.click('button[class="px-5 py-3 text-sm font-medium rounded-lg flex items-center justify-between flex-1 transition-all bg-white border-2 border-blue-500 text-blue-600"]'); |
|
} catch (error) { |
|
console.log("Tombol 'White Background' tidak ditemukan atau tidak terlihat, melewati langkah ini."); |
|
} |
|
|
|
try { |
|
await page.waitForSelector('button[class="w-full mt-5 px-5 py-3 text-sm font-medium rounded-lg transition-all bg-blue-500 text-white hover:bg-blue-600"]', { visible: true }); |
|
await page.click('button[class="w-full mt-5 px-5 py-3 text-sm font-medium rounded-lg transition-all bg-blue-500 text-white hover:bg-blue-600"]'); |
|
} catch (error) { |
|
console.log("Tombol 'Download Image' tidak ditemukan atau tidak terlihat, melewati langkah ini."); |
|
} |
|
|
|
await page.waitForSelector('canvas[class="absolute top-0 left-0 w-full h-full bg-white shadow-inner"]', { visible: true }); |
|
await page.waitForTimeout(800); |
|
|
|
const canvas = await page.$('canvas[class="absolute top-0 left-0 w-full h-full bg-white shadow-inner"]'); |
|
const screenshot = await canvas.screenshot(); |
|
|
|
res.setHeader('Content-Type', 'image/png'); |
|
res.status(200).send(screenshot); |
|
|
|
} catch (error) { |
|
return res.status(500).json({ error: 'Gagal memproses permintaan.', details: error.message }); |
|
} finally { |
|
await browser.close(); |
|
} |
|
}); |
|
|
|
app.get('/', async (req, res) => { |
|
res.status(200).send('Maker API'); |
|
}); |
|
|
|
const PORT = process.env.PORT || 7860; |
|
|
|
app.listen(PORT, async () => { |
|
console.log(`Server running on port ${PORT}`); |
|
await utils.initialize(); |
|
}); |
|
|
|
process.on('SIGINT', async () => { |
|
await utils.close(); |
|
process.exit(0); |
|
}); |