swgen / index.js
Ruloaooa's picture
Update index.js
756f3de verified
const express = require('express');
const { createCanvas, loadImage } = require('canvas');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit')
const bodyParser = require('body-parser');
const path = require('path');
const loadImg = require('./loader');
const app = express();
const { chromium } = require('playwright');
const port = 7860;
app.set('trust proxy', 1);
let totalReq = 0;
//Brat
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();
}
};
//Security
app.use(helmet());
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 menit
max: 100, // Membatasi 100 permintaan per IP dalam 15 menit
message: 'Terlalu banyak permintaan dari IP ini, coba lagi nanti.',
});
app.use(limiter);
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
/**
* Fungsi untuk menghasilkan gambar status kustom
* @param {string} profileImage - URL gambar profil
* @param {string} mainImage - URL gambar utama
* @param {string} caption - Teks caption
* @param {number} views - Jumlah tayangan
* @returns {Promise<Buffer>} - Buffer gambar dalam format PNG
*/
async function createCustomSWGenerator({ profileImage, mainImage, caption = "Custom Caption", views = 4 }) {
const canvasWidth = 1080;
const canvasHeight = 1920;
const canvas = createCanvas(canvasWidth, canvasHeight);
const ctx = canvas.getContext("2d");
// Background hitam
ctx.fillStyle = "#000000";
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
// Tambahkan gambar utama
if (mainImage) {
try {
const mainImg = await loadImg(mainImage);
ctx.drawImage(mainImg, 0, 200, canvasWidth, 1080);
} catch (error) {
console.error("Gagal memuat gambar utama:", error.message);
}
}
// Header background
ctx.fillStyle = "rgba(0, 0, 0, 0.7)";
ctx.fillRect(0, 0, canvasWidth, 180);
// Gambar profil dengan border putih
if (profileImage) {
try {
const profileImg = await loadImg(profileImage);
ctx.save();
ctx.beginPath();
ctx.arc(90, 90, 60, 0, Math.PI * 2);
ctx.closePath();
ctx.clip();
// Gambar profil
ctx.drawImage(profileImg, 30, 30, 120, 120);
// Garis putih di luar profil
ctx.beginPath();
ctx.arc(90, 90, 62, 0, Math.PI * 2); // Radius sedikit lebih besar
ctx.strokeStyle = "white";
ctx.lineWidth = 10; // Stroke putih tipis
ctx.stroke();
ctx.closePath();
ctx.restore();
} catch (error) {
console.error("Gagal memuat gambar profil:", error.message);
}
}
// Teks header
ctx.fillStyle = "#FFFFFF";
ctx.font = "50px 'Roboto Sans', Arial"; // Gunakan Roboto Sans jika tersedia
ctx.fillText("My status", 180, 80);
ctx.fillStyle = "#CCCCCC";
ctx.font = "30px 'Roboto Sans', Arial"; // Gunakan Roboto Sans
ctx.fillText("Now", 180, 130);
// Caption teks
ctx.fillStyle = "#FFFFFF";
ctx.font = "40px 'Roboto Sans', Arial";
ctx.textAlign = "center";
ctx.fillText(caption, canvasWidth / 2, 1400);
// Footer background
ctx.fillStyle = "rgba(0, 0, 0, 0.7)";
ctx.fillRect(0, 1620, canvasWidth, 300);
// Ikon dan teks footer
try {
const viewIcon = await loadImage('./eye.png'); // Ganti dengan URL ikon tayangan
const shareIcon = await loadImage('./share.png'); // Ganti dengan URL ikon bagikan
const promoteIcon = await loadImage('./promotion.png'); // Ganti dengan URL ikon promosikan
// "Tayangan"
ctx.drawImage(viewIcon, 80, 1660, 70, 70); // Ikon tayangan
ctx.fillStyle = "#FFFFFF";
ctx.font = "30px 'Roboto Sans', Arial";
ctx.textAlign = "center";
ctx.fillText(`${views} Views`, 170, 1780);
// "Promosikan"
ctx.drawImage(promoteIcon, 460, 1660, 70, 70); // Ikon promosikan
ctx.fillText("Promote", 550, 1780);
// "Bagikan"
ctx.drawImage(shareIcon, 840, 1660, 70, 70); // Ikon bagikan
ctx.fillText("Share", 920, 1780);
} catch (error) {
console.error("Gagal memuat ikon:", error.message);
}
// Kembalikan gambar sebagai Buffer PNG
return canvas.toBuffer('image/png');
}
//Threads
async function generateThread(username, avatarPath, textContent, countLike = "100") {
const canvas = createCanvas(1080, 300);
const ctx = canvas.getContext('2d');
// Background putih
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Teks "Utas pertama" dengan ikon bintang di bagian atas kiri
const starIcon = await loadImage('./star.svg');
ctx.drawImage(starIcon, 115, 20, 25, 25);
ctx.font = 'normal 25px Arial';
ctx.fillStyle = '#888888';
ctx.fillText('First thread', 145, 40);
// Titik tiga horizontal
const dotSize = 8;
const dotsXStart = canvas.width - 100;
const dotsY = 110;
for (let i = 0; i < 3; i++) {
ctx.beginPath();
ctx.arc(dotsXStart + i * 20, dotsY, dotSize / 2, 0, Math.PI * 2);
ctx.fillStyle = '#888888';
ctx.fill();
}
// Load avatar
const avatar = await loadImg(avatarPath);
const avatarSize = 80;
const avatarX = 40;
const avatarY = 80;
ctx.save();
ctx.beginPath();
ctx.arc(avatarX + avatarSize / 2, avatarY + avatarSize / 2, avatarSize / 2, 0, Math.PI * 2);
ctx.closePath();
ctx.clip();
ctx.drawImage(avatar, avatarX, avatarY, avatarSize, avatarSize);
ctx.restore();
// Nama pengguna
const dUser = username.length > 15 ? username.slice(0, 15) + '...' : username;
ctx.font = 'bold 30px Arial';
ctx.fillStyle = '#000000';
ctx.fillText(dUser, 130, 110);
const usernameWidth = ctx.measureText(dUser).width;
// Tulisan 'sekarang'
ctx.font = 'normal 25px Arial';
ctx.fillStyle = '#888888';
ctx.fillText('Now', 130 + usernameWidth + 10, 110);
// Konten teks
ctx.font = 'normal 35px Arial';
ctx.fillStyle = '#000000';
const maxWidth = canvas.width - 40;
const lineHeight = 40;
wrapText(ctx, textContent, 40, 210, maxWidth, lineHeight);
// Reaksi
const heart = await loadImage('./heart.svg');
ctx.drawImage(heart, 45, 250, 35, 35);
ctx.font = 'normal 25px Arial';
ctx.fillStyle = '#000000';
ctx.fillText(countLike, 90, 275);
return canvas.toBuffer('image/png');
}
// Fungsi untuk membungkus teks panjang
function wrapText(ctx, text, x, y, maxWidth, lineHeight) {
const words = text.split(' ');
let line = '';
for (let n = 0; n < words.length; n++) {
const testLine = line + words[n] + ' ';
const metrics = ctx.measureText(testLine);
const testWidth = metrics.width;
if (testWidth > maxWidth && n > 0) {
ctx.fillText(line, x, y);
line = words[n] + ' ';
y += lineHeight;
} else {
line = testLine;
}
}
ctx.fillText(line, x, y);
}
/**
* Endpoint untuk membuat gambar status
* @route POST /generate-status
* @param {string} profileImage - URL gambar profil
* @param {string} mainImage - URL gambar utama
* @param {string} caption - Teks caption
* @param {number} views - Jumlah tayangan
* @returns {Buffer} - Gambar dalam format PNG
*/
app.get('/', (req, res) => {
const documentation = {
description: 'API documentation for generating image using canvas',
totalRequest: totalReq,
endpoints: [
{
method: 'POST',
path: '/generate',
description: 'Generate a new WhatsApp status with profile image, main image, caption, and views.',
requestBody: {
profileImage: 'URL to the profile image',
mainImage: 'URL to the main image',
caption: 'Caption for the status',
views: 'Number of views (integer)',
},
exampleRequest: {
profileImage: 'https://example.com/image.jpg',
mainImage: 'https://example.com/image2.jpg',
caption: 'this is the caption!',
views: 10,
},
response: {
status: 'success',
ContentType: 'image/png'
},
},
{
method: 'POST',
path: '/generate2',
description: 'Generate treads comments with avatar, username, text, and countLike.',
requestBody: {
avatar: 'URL to the profile image',
username: 'username for profile',
text: 'main text',
countLike: 'total like count',
},
exampleRequest: {
avatar: 'https://example.com/image.jpg',
username: '4rlzyy',
text: 'this is the main text!',
countLike: 10,
},
response: {
status: 'success',
ContentType: 'image/png'
},
},
{
method: 'GET',
path: '/brat',
description: 'Generate brat image using text query.',
request: {
text: 'input query'
},
exampleRequest: {
text: 'hii iam usinv brat.'
},
response: {
status: 'success',
ContentType: 'image/png'
},
},
],
};
res.json(documentation);
});
app.get('/brat', async (req, res) => {
try {
const { text } = req.query;
totalReq++;
// Validasi jika parameter `text` tidak ada
if (!text || typeof text !== 'string' || text.trim().length === 0) {
return res.status(400).json({
message: 'Invalid input. Please provide a valid text query parameter.'
});
}
// Proses pembuatan gambar
const imageBuffer = await utils.generateBrat(text);
// Mengirimkan hasil gambar
res.set('Content-Type', 'image/png');
res.send(imageBuffer);
} catch (error) {
console.error('Error generating brat:', error.message);
res.status(500).json({
message: 'An error occurred while generating the brat image.',
error: error.message
});
}
});
app.post('/generate', async (req, res) => {
const { profileImage, mainImage, caption, views } = req.body;
totalReq++;
if (!profileImage || !mainImage) {
return res.status(400).json({ error: "Gambar profil dan gambar utama harus disediakan." });
}
try {
const imageBuffer = await createCustomSWGenerator({ profileImage, mainImage, caption, views });
res.setHeader('Content-Type', 'image/png');
res.send(imageBuffer);
} catch (error) {
console.error(error);
res.status(500).json({ error: "Gagal membuat gambar." });
}
});
app.post('/generate2', async (req, res) => {
try {
const { username, avatar, text, countLike } = req.body;
totalReq++;
// Validasi input
if (!username || !text || !avatar) {
return res.status(400).json({ error: 'Username, avatar and text are required.' });
}
const imageBuffer = await generateThread(username, avatar, text, countLike);
// Mengirimkan hasil sebagai gambar
res.setHeader('Content-Type', 'image/png');
res.send(imageBuffer);
} catch (error) {
console.error('Error generating thread:', error);
res.status(500).json({ error: 'An error occurred while generating the thread.' });
}
});
// Mulai server Express
app.listen(port, async () => {
console.log(`Server berjalan di http://localhost:${port}`);
await utils.initialize();
});
process.on('SIGINT', async () => {
process.exit(0);
});