/* 🍁 Dont forget install module : @napi-rs/canvas πŸ‚Author canvas : Ditxz πŸƒ Author plugins : Lynx decode - Inf project 🌸 Note : brat canvas, belum jadi sticker result masih foto sisanya atur saja.. jangan hapus wm kocak !! */ import { createCanvas, loadImage, GlobalFonts } from '@napi-rs/canvas' import { writeFileSync, existsSync, readFileSync } from 'fs' import path from 'path' import { fileURLToPath } from 'url' const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) const FONT_URL = 'https://raw.githubusercontent.com/Ditzzx-vibecoder/Assets/main/Font/ARIALN.ttf' const EMOJI_JSON_URL = 'https://media.githubusercontent.com/media/Ditzzx-vibecoder/entahlah/main/emoji-apple.json' const FONT_PATH = path.join(__dirname, 'ARIALN.ttf') const EMOJI_JSON_PATH = path.join(__dirname, 'emoji-apple.json') const THEMES = { black: { bg: '#000000', text: '#ffffff' }, white: { bg: '#ffffff', text: '#000000' }, green: { bg: '#8ace00', text: '#000000' } } let emojiMap = null const emojiImageCache = new Map() async function downloadFile(url, dest) { const res = await fetch(url) if (!res.ok) throw new Error(`Gagal download: ${url}`) const buf = Buffer.from(await res.arrayBuffer()) writeFileSync(dest, buf) return buf } async function ensureFont() { if (!existsSync(FONT_PATH)) await downloadFile(FONT_URL, FONT_PATH) GlobalFonts.registerFromPath(FONT_PATH, 'ArialNarrow') } function emojiToUnicode(emoji) { return [...emoji].map(c => c.codePointAt(0).toString(16).padStart(4, '0')).join('-') } async function loadEmojiMap() { if (emojiMap) return emojiMap if (!existsSync(EMOJI_JSON_PATH)) await downloadFile(EMOJI_JSON_URL, EMOJI_JSON_PATH) emojiMap = JSON.parse(readFileSync(EMOJI_JSON_PATH, 'utf-8')) return emojiMap } async function getEmojiImage(emoji) { if (emojiImageCache.has(emoji)) return emojiImageCache.get(emoji) const map = await loadEmojiMap() const base = emojiToUnicode(emoji) const variants = [ base, base.replace(/-fe0f/gi, ''), `${base.replace(/-fe0f/gi, '')}-fe0f`, base.toUpperCase(), base.replace(/-fe0f/gi, '').toUpperCase(), base.replace(/-fe0f/gi, '').toUpperCase() + '-FE0F' ] let b64 = null for (const v of variants) { if (map[v]) { b64 = map[v]; break } } if (!b64) return null const img = await loadImage(Buffer.from(b64, 'base64')) emojiImageCache.set(emoji, img) return img } async function drawAppleEmoji(ctx, emoji, x, y, size) { const img = await getEmojiImage(emoji) if (!img) { ctx.fillText(emoji, x, y); return } ctx.drawImage(img, x, y, size, size) } const EMOJI_REGEX = /(\p{Emoji_Modifier_Base}\p{Emoji_Modifier}|\p{Emoji_Presentation}\uFE0F?|\p{Emoji}\uFE0F|[\u{1F1E0}-\u{1F1FF}]{2}|\p{Extended_Pictographic}\uFE0F?)/gu function measureTextCustom(ctx, text, fontSize) { const parts = text.split(EMOJI_REGEX) let w = 0 for (const part of parts) { if (!part) continue EMOJI_REGEX.lastIndex = 0 if (EMOJI_REGEX.test(part)) w += fontSize else w += ctx.measureText(part).width EMOJI_REGEX.lastIndex = 0 } return w } async function drawTextWithEmojis(ctx, text, x, y, fontSize) { const parts = text.split(EMOJI_REGEX) let curX = x for (const part of parts) { if (!part) continue EMOJI_REGEX.lastIndex = 0 if (EMOJI_REGEX.test(part)) { await drawAppleEmoji(ctx, part, curX, y, fontSize) curX += fontSize } else { ctx.fillText(part, curX, y) curX += ctx.measureText(part).width } EMOJI_REGEX.lastIndex = 0 } } function wrapText(ctx, text, maxWidth, fontSize) { ctx.font = `${fontSize}px ArialNarrow` const words = text.split(' ') const lines = [] let cur = '' for (const word of words) { const test = cur ? cur + ' ' + word : word if (measureTextCustom(ctx, test, fontSize) > maxWidth && cur) { lines.push(cur) cur = word } else { cur = test } } if (cur) lines.push(cur) return lines } function fitsAt(ctx, text, fontSize, maxWidth, maxHeight, lineGap) { const lines = wrapText(ctx, text, maxWidth, fontSize) const longestWord = Math.max(...text.split(' ').map(w => measureTextCustom(ctx, w, fontSize))) const totalHeight = lines.length * (fontSize + lineGap) - lineGap return longestWord <= maxWidth && totalHeight <= maxHeight } function findBestFontSize(ctx, text, maxWidth, maxHeight, lineGap) { let lo = 10 let hi = 700 let best = lo while (lo <= hi) { const mid = Math.floor((lo + hi) / 2) if (fitsAt(ctx, text, mid, maxWidth, maxHeight, lineGap)) { best = mid lo = mid + 1 } else { hi = mid - 1 } } return best } async function generateBratBuffer({ text = 'Brat', theme = 'white', blur = 0 } = {}) { const selectedTheme = THEMES[theme] || THEMES.white const blurAmount = [0, 1, 2, 3].includes(blur) ? blur : 0 const size = 1000 const padding = 80 const lineGap = 20 const maxWidth = size - padding * 2 const maxHeight = size - padding * 2 await ensureFont() await loadEmojiMap() const canvas = createCanvas(size, size) const ctx = canvas.getContext('2d') const fontSize = findBestFontSize(ctx, text, maxWidth, maxHeight, lineGap) const lines = wrapText(ctx, text, maxWidth, fontSize) ctx.fillStyle = selectedTheme.bg ctx.fillRect(0, 0, size, size) ctx.fillStyle = selectedTheme.text ctx.font = `${fontSize}px ArialNarrow` ctx.textAlign = 'left' ctx.textBaseline = 'top' ctx.save() if (blurAmount > 0) ctx.filter = `blur(${blurAmount}px)` const totalTextHeight = lines.length * (fontSize + lineGap) - lineGap let y = (size - totalTextHeight) / 2 for (const line of lines) { await drawTextWithEmojis(ctx, line, padding, y, fontSize) y += fontSize + lineGap } ctx.restore() return await canvas.encode('png') } let handler = async (m, { conn, text, usedPrefix, command }) => { if (!text) { return m.reply(`❌ Teksnya mana bre?\n\n*Contoh:* ${usedPrefix + command} Brat Canvas 🎨`) } await m.react('⏳') let loadMsg = await conn.sendMessage(m.chat, { text: 'πŸ”„ *Membuat Brat...*' }, { quoted: m }) try { const imageBuffer = await generateBratBuffer({ text, theme: 'white', blur: 0 }) await conn.sendMessage(m.chat, { image: imageBuffer, caption: `✨ *Selesai bre!*\n> Β© Erine Brat Maker` }, { quoted: m }) await conn.sendMessage(m.chat, { delete: loadMsg.key }) await m.react('βœ…') } catch (e) { console.error('[BRAT MAKER ERROR]', e) await conn.sendMessage(m.chat, { text: `❌ *Gagal:* ${e.message}`, edit: loadMsg.key }).catch(() => {}) await m.react('❌') } } handler.help = ['brat '] handler.tags = ['sticker'] handler.command = /^(brat)$/i handler.limit = true handler.register = true export default handler