تخطَّ إلى المحتوى
ARAB CODEمجتمع المطورين العرب

كود حزمه يجيب لك حزكة ملصقات من بنترست

javascriptv1MITنُشر ٢٨ يوليو ٢٠٢٦· ♥ 0 · ⬇ 1 · 💬 0

تحميل ملصقات

// plugins/stickers-pack.js
// 🕷️ Spider-Man - حزمة ملصقات Pinterest 📦

import fetch from 'node-fetch'
import { writeFileSync, unlinkSync, readFileSync, existsSync } from 'fs'
import { execSync } from 'child_process'
import crypto from 'crypto'
import https from 'https'
import axios from 'axios'

const PINTEREST_BASE = 'https://www.pinterest.com'
const PINTEREST_SEARCH_PATH = '/resource/BaseSearchResource/get/'
const PINTEREST_HEADERS = {
   accept: 'application/json, text/javascript, */*, q=0.01',
   referer: 'https://www.pinterest.com/',
   'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36',
   'x-app-version': 'a9522f',
   'x-pinterest-appstate': 'active',
   'x-pinterest-pws-handler': 'www/[username]/[slug].js',
   'x-requested-with': 'XMLHttpRequest'
}

async function getPinterestCookies() {
   try {
      const res = await axios.get(PINTEREST_BASE, { timeout: 15000 })
      const setCookies = res.headers['set-cookie']
      if (!setCookies) return null
      return setCookies.map(c => c.split(';')[0].trim()).join('; ')
   } catch {
      return null
   }
}

async function searchPinterest(query, needed = 100) {
   const images = { static: [], animated: [] }
   const cookies = await getPinterestCookies()
   if (!cookies) return images

   let bookmarks = ['']
   let attempts = 0
   const maxAttempts = 10

   while (images.static.length + images.animated.length < needed && bookmarks.length && attempts < maxAttempts) {
      attempts++
      try {
         const params = {
            source_url: `/search/pins/?q=${encodeURIComponent(query)}`,
            data: JSON.stringify({
               options: { isPrefetch: false, query, scope: 'pins', bookmarks, page_size: 50 },
               context: {}
            }),
            _: Date.now()
         }

         const { data } = await axios.get(`${PINTEREST_BASE}${PINTEREST_SEARCH_PATH}`, {
            headers: { ...PINTEREST_HEADERS, cookie: cookies },
            params,
            timeout: 15000
         })

         const results = data?.resource_response?.data?.results?.filter(v => v?.images?.orig?.url) || []
         if (!results.length) break

         for (const r of results) {
            const url = r.images.orig.url
            if (url.endsWith('.gif')) {
               images.animated.push(url)
            } else {
               images.static.push(url)
            }
            if (images.static.length + images.animated.length >= needed) break
         }

         const nextBookmark = data?.resource_response?.bookmark
         if (!nextBookmark || nextBookmark === '-end-') break
         bookmarks = [nextBookmark]
      } catch {
         break
      }
   }

   return {
      static: [...new Set(images.static)].slice(0, needed),
      animated: [...new Set(images.animated)].slice(0, needed)
   }
}

async function getSharp() {
  try {
    const mod = await import('sharp')
    return mod.default
  } catch {
    throw new Error('❌ مكتبة sharp غير مثبتة\n📦 npm i sharp')
  }
}

async function makeTrayWebp(buffer) {
  const sharp = await getSharp()
  return sharp(buffer, { animated: false }).resize(252, 252, { fit: 'cover' }).webp().toBuffer()
}

async function makeBlankTrayWebp() {
  const sharp = await getSharp()
  return sharp({ create: { width: 252, height: 252, channels: 4, background: { r: 0, g: 0, b: 0, alpha: 0 } } }).webp().toBuffer()
}

async function makeThumbnailJpeg(buffer) {
  const sharp = await getSharp()
  return sharp(buffer).resize(252, 252, { fit: 'cover' }).jpeg().toBuffer()
}

function sha256(buffer) {
  return crypto.createHash('sha256').update(buffer).digest()
}

function toB64Url(buffer) {
  return Buffer.from(buffer).toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '')
}

async function uploadToServer(conn, buffer, { hkdf, mediaPath, mediaKey = crypto.randomBytes(32) }) {
  const expanded = Buffer.from(crypto.hkdfSync('sha256', mediaKey, Buffer.alloc(32), Buffer.from(hkdf), 112))
  const iv = expanded.subarray(0, 16)
  const cipherKey = expanded.subarray(16, 48)
  const macKey = expanded.subarray(48, 80)
  const cipher = crypto.createCipheriv('aes-256-cbc', cipherKey, iv)
  const encrypted = Buffer.concat([cipher.update(buffer), cipher.final()])
  const mac = crypto.createHmac('sha256', macKey).update(iv).update(encrypted).digest().subarray(0, 10)
  const encBuffer = Buffer.concat([encrypted, mac])
  const fileSha256 = sha256(buffer)
  const fileEncSha256 = sha256(encBuffer)

  const iq = await conn.query({
    tag: 'iq', attrs: { id: conn.generateMessageTag?.() ?? Date.now().toString(), to: 's.whatsapp.net', type: 'set', xmlns: 'w:m' },
    content: [{ tag: 'media_conn', attrs: {} }]
  })

  const mediaConn = iq.content?.find(v => v.tag === 'media_conn')
  if (!mediaConn) throw new Error('media_conn غير موجود')
  const auth = mediaConn.attrs?.auth
  if (!auth) throw new Error('auth غير موجود')

  const hosts = (mediaConn.content || []).filter(v => v.tag === 'host').map(v => v.attrs?.hostname).filter(Boolean)
  if (!hosts.length) throw new Error('لا يوجد host للرفع')

  const token = encodeURIComponent(fileEncSha256.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, ''))

  for (const host of hosts) {
    try {
      const json = await new Promise((resolve, reject) => {
        const url = new URL(`https://${host}${mediaPath}/${token}?auth=${encodeURIComponent(auth)}&token=${token}`)
        const req = https.request({
          hostname: url.hostname, port: 443, path: url.pathname + url.search, method: 'POST',
          headers: { Origin: 'https://web.whatsapp.com', Referer: 'https://web.whatsapp.com/', 'Content-Type': 'application/octet-stream', 'Content-Length': encBuffer.length }
        }, (res) => {
          let body = ''
          res.on('data', c => body += c)
          res.on('end', () => {
            if (res.statusCode < 200 || res.statusCode >= 300) return reject(new Error(`فشل الرفع ${res.statusCode}`))
            try { resolve(JSON.parse(body)) } catch { reject(new Error('رد غير JSON')) }
          })
        })
        req.on('error', reject)
        req.write(encBuffer)
        req.end()
      })
      const directPath = json.direct_path ?? json.directPath ?? json.url ?? json.path
      if (!directPath) throw new Error('directPath غير موجود')
      return { mediaKey, fileLength: buffer.length, fileSha256, fileEncSha256, directPath, ...json }
    } catch (e) { lastError = e }
  }
  throw lastError ?? new Error('جميع محاولات الرفع فشلت')
}

async function sendStickerPack(conn, m, pack, query) {
  const JSZip = (await import('jszip')).default
  const zip = new JSZip()
  const stickersMetadata = []

  for (const item of pack) {
    const fileName = `${toB64Url(sha256(item.buffer))}.webp`
    zip.file(fileName, item.buffer)
    stickersMetadata.push({
      fileName,
      isAnimated: item.isAnimated,
      emojis: [''],
      accessibilityLabel: '',
      isLottie: false,
      mimetype: 'image/webp'
    })
  }

  const trayIconFileName = 'tray_icon.webp'
  const traySource = pack.find(v => !v.isAnimated)?.buffer || pack[0]?.buffer
  const trayBuffer = traySource ? await makeTrayWebp(traySource) : await makeBlankTrayWebp()
  zip.file(trayIconFileName, trayBuffer)

  const archive = await zip.generateAsync({ type: 'nodebuffer', compression: 'STORE' })

  const packUpload = await uploadToServer(conn, archive, { hkdf: 'WhatsApp Sticker Pack Keys', mediaPath: '/mms/sticker-pack' })
  const thumbnailBuffer = await makeThumbnailJpeg(trayBuffer)
  const thumbUpload = await uploadToServer(conn, thumbnailBuffer, { hkdf: 'WhatsApp Sticker Pack Thumbnail Keys', mediaPath: '/mms/thumbnail-sticker-pack', mediaKey: packUpload.mediaKey })

  await conn.relayMessage(m.chat, {
    messageContextInfo: { messageSecret: crypto.randomBytes(32) },
    stickerPackMessage: {
      stickerPackId: 'Pack_' + crypto.randomBytes(8).toString('hex'),
      name: '🕷️ Spider-Man',
      publisher: '🕷️ Spider-Man',
      packDescription: `🕷️ ${query}`,
      stickers: stickersMetadata,
      fileLength: packUpload.fileLength,
      fileSha256: packUpload.fileSha256,
      fileEncSha256: packUpload.fileEncSha256,
      mediaKey: packUpload.mediaKey,
      directPath: packUpload.directPath,
      mediaKeyTimestamp: Math.floor(Date.now() / 1000),
      stickerPackSize: packUpload.fileLength,
      stickerPackOrigin: 2,
      trayIconFileName,
      thumbnailDirectPath: thumbUpload.directPath,
      thumbnailSha256: thumbUpload.fileSha256,
      thumbnailEncSha256: thumbUpload.fileEncSha256,
      thumbnailHeight: 252,
      thumbnailWidth: 252,
      imageDataHash: thumbUpload.fileSha256.toString('base64')
    }
  }, { quoted: m })
}

async function makeWebp(buffer, index) {
  const ts = Date.now()
  const inputPath = `./tmp_pk_${ts}_${index}.jpg`
  const outputPath = `./tmp_pk_${ts}_${index}.webp`
  try {
    writeFileSync(inputPath, buffer)
    execSync(`ffmpeg -y -i "${inputPath}" -vf "scale=512:512:force_original_aspect_ratio=decrease,pad=512:512:(ow-iw)/2:(oh-ih)/2:color=0x00000000" -q:v 80 "${outputPath}"`, { stdio: 'pipe' })
    let result = readFileSync(outputPath)
    return { buffer: result, isAnimated: false }
  } finally {
    try { if (existsSync(inputPath)) unlinkSync(inputPath) } catch {}
    try { if (existsSync(outputPath)) unlinkSync(outputPath) } catch {}
  }
}

async function makeAnimatedWebp(buffer, index) {
  const ts = Date.now()
  const inputPath = `./tmp_pk_a_${ts}_${index}.gif`
  const outputPath = `./tmp_pk_a_${ts}_${index}.webp`
  try {
    writeFileSync(inputPath, buffer)
    execSync(`ffmpeg -y -i "${inputPath}" -vf "scale=512:512:force_original_aspect_ratio=decrease,pad=512:512:(ow-iw)/2:(oh-ih)/2:color=0x00000000,fps=15" -loop 0 -q:v 80 -preset default -an "${outputPath}"`, { stdio: 'pipe' })
    let result = readFileSync(outputPath)
    return { buffer: result, isAnimated: true }
  } finally {
    try { if (existsSync(inputPath)) unlinkSync(inputPath) } catch {}
    try { if (existsSync(outputPath)) unlinkSync(outputPath) } catch {}
  }
}

let handler = async (m, { conn, text, usedPrefix, command }) => {

  if (!text) {
    return m.reply(`🕷️ ${usedPrefix + command} <بحث> <عدد>\n🕷️ مثال: ${usedPrefix + command} anime 10`)
  }

  let query = text
  let count = 10
  const parts = text.split(' ')
  const lastPart = parts[parts.length - 1]
  if (!isNaN(lastPart) && parts.length > 1) {
    count = Math.min(100, Math.max(1, parseInt(lastPart)))
    query = parts.slice(0, -1).join(' ')
  }

  let statusMsg = await m.reply(`🕷️ ${m.pushName} بحث عن ${query}`)

  try {
    const images = await searchPinterest(query, count)
    const allStatic = images.static
    const allAnimated = images.animated

    if (allStatic.length === 0 && allAnimated.length === 0) {
      return m.reply(`🕷️ لا توجد نتائج لـ ${query}`)
    }

    const pack = []
    const UA = 'Mozilla/5.0 (Linux; Android 10) AppleWebKit/537.36'

    for (let i = 0; i < allStatic.length && pack.length < count; i++) {
      try {
        const res = await fetch(allStatic[i], { headers: { 'User-Agent': UA }, timeout: 10000 })
        if (!res.ok) continue
        const buffer = Buffer.from(await res.arrayBuffer())
        const webp = await makeWebp(buffer, i)
        pack.push(webp)
      } catch {}
    }

    for (let i = 0; i < allAnimated.length && pack.length < count; i++) {
      try {
        const res = await fetch(allAnimated[i], { headers: { 'User-Agent': UA }, timeout: 15000 })
        if (!res.ok) continue
        const buffer = Buffer.from(await res.arrayBuffer())
        const webp = await makeAnimatedWebp(buffer, i)
        pack.push(webp)
      } catch {}
    }

    if (pack.length === 0) {
      return m.reply(`🕷️ تعذر معالجة اي ملصق`)
    }

    try { await conn.sendMessage(m.chat, { delete: statusMsg.key }) } catch {}

    await sendStickerPack(conn, m, pack, query)

  } catch (e) {
    console.error(e)
    try { await conn.sendMessage(m.chat, { delete: statusMsg.key }) } catch {}
    m.reply(`🕷️ خطأ: ${e.message}`)
  }
}

handler.command = ['ملصقات', 'باكج', 'stickerpack', 'pack']
handler.help = ['ملصقات [بحث] [عدد]']
handler.tags = ['sticker']

export default handler

التعليقات (0)

سجّل الدخول للتعليق.