Spaces:
Paused
Paused
File size: 12,568 Bytes
bdb50d6 9b50b65 d6c8688 ad2e61e e142ee1 fdf7b36 53cf329 62b2b51 e142ee1 ae419f1 fdf7b36 44f6058 e142ee1 ad2e61e e142ee1 ad2e61e e142ee1 257d7ee e142ee1 257d7ee e142ee1 2e10d12 e142ee1 257d7ee e142ee1 257d7ee e142ee1 257d7ee e142ee1 8e1d7ec b11b3e5 8e1d7ec 526907b 8e1d7ec a3a2687 8e1d7ec e142ee1 d6c8688 0309e6f 4319df5 faae3d2 ae419f1 4319df5 2fe8d1c 4319df5 1f2ed94 4319df5 1f2ed94 cfa692e 4319df5 d6c8688 b24d16e 756f3de b24d16e a2cd5ac e142ee1 ae419f1 e142ee1 8e1d7ec 1f2ed94 ae419f1 8e1d7ec ded9aae 8e1d7ec 24d8fa5 8e1d7ec 4734a74 8e1d7ec b24d16e 9d2a071 e142ee1 00ad529 e142ee1 5c299c6 e142ee1 41cd8b4 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 |
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);
});
|