bratcanvas
#hx4cvbzjavascript Β· 230 baris Β· 28 dilihat
/*
π 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 <teks>']
handler.tags = ['sticker']
handler.command = /^(brat)$/i
handler.limit = true
handler.register = true
export default handler