Lynx Snip

bratcanvas

#hx4cvbz

javascript Β· 230 baris Β· 28 dilihat

Lihat raw
/*

🍁 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