// كود بحث وتحميل انمي بجودات // https://whatsapp.com/channel/0029Vb7Nq294Y9le1aAcTE0D // تابعو القناة هننشر اكواد تانية "izana,uncel shawaza" import { generateWAMessageFromContent, prepareWAMessageMedia } from '@whiskeysockets/baileys' import axios from 'axios' import crypto from 'crypto' const API_BASE = 'https://engez.a7a.online/api/v1' const SEARCH_ENDPOINT = `${API_BASE}/anime/animev2` const DOWNLOADER_ENDPOINT = `${API_BASE}/download/downloader` const SELECT_SEPARATOR = '|' const CACHE_TTL_MS = 3 * 60 * 1000 const DEFAULT_IMAGE = 'https://i.postimg.cc/bS01zQwK/upload-1767808833485.jpg' const contextCache = new Map() function makeToken() { return crypto.randomBytes(8).toString('hex') } function now() { return Date.now() } function isExpired(entry) { return !entry || (now() - entry.timestamp) > CACHE_TTL_MS } function setContext(data) { const token = makeToken() contextCache.set(token, { ...data, timestamp: now() }) return token } function getContext(token) { const entry = contextCache.get(token) if (isExpired(entry)) { contextCache.delete(token) return null } return entry } function clearStaleContexts() { const ts = now() for (const [token, entry] of contextCache.entries()) { if (!entry || (ts - entry.timestamp) > CACHE_TTL_MS) { contextCache.delete(token) } } } function isYouTubeUrl(url) { return /^(https?:\/\/)?(www\.)?(youtube\.com|youtu\.be|m\.youtube\.com)\//i.test(url) } function cleanText(text = '') { return String(text) .replace(/\[?\s*witanime(?:\.com)?\s*\]?/ig, '') .replace(/\s+/g, ' ') .replace(/\s*[-–—:|]+\s*$/g, '') .replace(/^\s*[-–—:|]+\s*/g, '') .trim() } function safeFileName(text = '') { return cleanText(text) .replace(/[\\/:*?"<>|]/g, '_') .replace(/\s+/g, ' ') .trim() } function decodeB64(value) { if (!value) return '' try { return Buffer.from(String(value), 'base64').toString('utf-8') } catch { return String(value) } } function describeAnimeItem(item, index) { const title = cleanText(item.title || 'بدون عنوان') const status = item.status || item.type || 'غير معروف' const type = item.type || 'غير معروف' const rating = item.rating != null ? Number(item.rating).toFixed(1) : 'غير معروف' return { header: `أنمي #${index + 1}`, title, description: `النوع: ${type} • التقييم: ${rating}` } } function describeEpisodeItem(item, index) { const number = item.number || String(index + 1) const title = cleanText(item.title || `الحلقة ${number}`) return { header: `حلقة #${number}`, title, description: `رقم الحلقة: ${number}` } } function describeLinkItem(item, index) { const server = item.server || 'غير معروف' const quality = item.quality || 'غير معروف' return { header: `رابط #${index + 1}`, title: `${server} • ${quality}`, description: `تحميل من ${server} بجودة ${quality}` } } async function sendSelectList(conn, chat, quoted, { title, footer, sections, posterUrl }) { let mediaMessage = null try { mediaMessage = await prepareWAMessageMedia( { image: { url: posterUrl || DEFAULT_IMAGE } }, { upload: conn.waUploadToServer } ) } catch { try { mediaMessage = await prepareWAMessageMedia( { image: { url: DEFAULT_IMAGE } }, { upload: conn.waUploadToServer } ) } catch { mediaMessage = null } } try { const msgContent = { viewOnceMessage: { message: { interactiveMessage: { body: { text: title }, footer: { text: footer }, header: mediaMessage ? { hasMediaAttachment: true, imageMessage: mediaMessage.imageMessage } : { hasMediaAttachment: false, title }, nativeFlowMessage: { buttons: [{ name: 'single_select', buttonParamsJson: JSON.stringify({ title: '◜⏤͟͞͞ أنمي ˖࣪⃟❄️◞•', sections }) }] } } } } } const waMsg = generateWAMessageFromContent(chat, msgContent, { userJid: conn.user.jid, quoted }) await conn.relayMessage(chat, waMsg.message, { messageId: waMsg.key.id }) } catch (e) { let txt = `${title}\n\n` for (const section of sections) { for (const row of section.rows) { txt += `${row.header}\n${row.title}\n${row.id}\n\n` } } await conn.sendMessage(chat, { text: txt }, { quoted }) } } async function fetchSearchResults(query) { clearStaleContexts() const apiUrl = `${SEARCH_ENDPOINT}?action=${encodeURIComponent('بحث')}&q=${encodeURIComponent(query)}` const { data } = await axios.get(apiUrl, { timeout: 30000 }) if (!data || data.success !== true) { throw new Error('لم يتم العثور على نتائج لهذا البحث') } if (!Array.isArray(data.response) || data.response.length === 0) { throw new Error('لا توجد نتائج مطابقة') } return data.response } async function fetchEpisodes(animeId) { clearStaleContexts() const apiUrl = `${SEARCH_ENDPOINT}?action=${encodeURIComponent('حلقات')}&id=${encodeURIComponent(animeId)}` const { data } = await axios.get(apiUrl, { timeout: 30000 }) if (!data || data.success !== true) { throw new Error('لم يتم العثور على حلقات لهذا الأنمي') } const response = data.response if (!response || !Array.isArray(response.episodes) || response.episodes.length === 0) { throw new Error('لا توجد حلقات متاحة لهذا الأنمي') } return { animeTitle: cleanText(response.animeTitle || 'بدون عنوان'), totalEpisodes: response.totalEpisodes || response.episodes.length, episodes: response.episodes } } function isPixeldrainLink(item) { const server = String(item?.server || '').toLowerCase() const tag = String(item?.tag || '').toLowerCase() const decodedLink = String(decodeB64(item?.link) || item?.link || '').toLowerCase() return ( server === 'pd' || tag === 'pd' || server.includes('pixeldrain') || tag.includes('pixeldrain') || decodedLink.includes('pixeldrain.com') ) } async function fetchEpisodeLinks(episodeId) { clearStaleContexts() const apiUrl = `${SEARCH_ENDPOINT}?action=${encodeURIComponent('روابط')}&id=${encodeURIComponent(episodeId)}` const { data } = await axios.get(apiUrl, { timeout: 30000 }) if (!data || data.success !== true) { throw new Error('لم يتم العثور على روابط لهذه الحلقة') } const links = data?.response?.links if (!Array.isArray(links) || links.length === 0) { throw new Error('لا توجد روابط متاحة لهذه الحلقة') } // نقتصر على روابط Pixeldrain بس ونستبعد أي مصدر تاني (MediaFire، إلخ) const pdLinks = links.filter(isPixeldrainLink) if (pdLinks.length === 0) { throw new Error('لا توجد روابط Pixeldrain متاحة لهذه الحلقة') } return pdLinks } async function resolveDownloadUrl(encodedOrRawLink) { let target = encodedOrRawLink if (!/^https?:\/\//i.test(target)) { target = decodeB64(target) } // خط دفاع أخير: لو الرابط (بعد فك التشفير) مش من Pixeldrain، نرفضه // هنا قبل ما نضرب الـ API أصلاً — بيغطي حالة روابط محفوظة قديمة في // الـ cache من قبل الفلترة، أو أي مسار تاني ممكن يوصل هنا بالغلط. if (!/pixeldrain\.com/i.test(target)) { throw new Error('التحميل مقتصر على Pixeldrain فقط، هذا الرابط من مصدر آخر') } const apiUrl = `${DOWNLOADER_ENDPOINT}?url=${encodeURIComponent(target)}` const { data } = await axios.get(apiUrl, { timeout: 30000 }) if (!data || data.success !== true || !data.response?.downloadUrl) { throw new Error('لم يتم الحصول على رابط التحميل المباشر') } return data.response } function extractPixeldrainId(url) { const patterns = [ /pixeldrain\.com\/u\/([a-zA-Z0-9]+)/, /pixeldrain\.com\/api\/file\/([a-zA-Z0-9]+)/, /^([a-zA-Z0-9]{8})$/ ] for (const pattern of patterns) { const match = String(url).trim().match(pattern) if (match) return match[1] } return null } async function downloadDirectFile(url) { // Pixeldrain بيرفض التحميل المباشر بـ hotlink_detected لو الطلب // جاي من غير Referer بيدل إنه صادر من صفحة Pixeldrain نفسها. // لازم نبعت نفس الـ headers اللي هاندلر ويتشر الأصلي بيستخدمها // (Referer + Sec-Fetch-*) عشان نتجاوز الحماية دي. const pdFileId = extractPixeldrainId(url) const headers = pdFileId ? { Accept: '*/*', 'Accept-Encoding': 'identity', Referer: `https://pixeldrain.com/u/${pdFileId}`, 'Sec-Fetch-Site': 'same-origin', 'Sec-Fetch-Mode': 'navigate', 'Sec-Fetch-Dest': 'empty', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36' } : { 'User-Agent': 'Mozilla/5.0 (Linux; Android 13; Mobile) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36', Accept: '*/*', 'Accept-Language': 'en-US,en;q=0.9' } const response = await axios.get(url, { responseType: 'arraybuffer', timeout: 60000, maxRedirects: 5, headers }) const contentType = String(response.headers?.['content-type'] || '').toLowerCase() // لو الرابط المباشر رجّع صفحة HTML بدل الملف الفعلي (مثلاً صفحة // خطأ MediaFire زي /error.php لما بيحصل rate-limit)، ده مش الملف // المطلوب فعلاً حتى لو الـ API قال success. نرفض هنا قبل ما نبعت // buffer بايظ للمستخدم على إنه فيديو أو ملف. if (contentType.includes('text/html')) { throw new Error('الرابط المباشر رجّع صفحة خطأ بدل الملف (على الأغلب rate limit أو hotlink detection من السيرفر المصدر)، حاول مرة أخرى بعد شوية') } // Pixeldrain أحيانًا بيرجع 200 مع body فيه JSON خطأ (زي // hotlink_detected) بدل ما يرجّع status code خطأ فعلي. نتأكد إن // اللي راجع مش JSON خطأ متنكر في شكل arraybuffer. if (contentType.includes('application/json') || contentType.includes('text/plain')) { let bodyText = '' try { bodyText = Buffer.from(response.data).toString('utf-8') } catch { bodyText = '' } if (/hotlink_detected|"success"\s*:\s*false/i.test(bodyText)) { throw new Error('Pixeldrain رفض التحميل بسبب hotlink detection — تأكد إن الـ Referer بيتبعت صح') } } return { buffer: Buffer.from(response.data), headers: response.headers || {}, contentType: response.headers?.['content-type'] || '' } } async function sendDownloadedMedia(conn, chat, quoted, { directUrl, sourceLabel, quality, animeTitle, episodeTitle, episodeNumber, fileNameFromApi }) { const { buffer, contentType } = await downloadDirectFile(directUrl) const mime = String(contentType).toLowerCase() const cleanAnime = safeFileName(animeTitle || 'Anime') const cleanEpisode = safeFileName(episodeTitle || `الحلقة ${episodeNumber || ''}`.trim()) const cleanQuality = quality ? safeFileName(quality) : '' const cleanSource = sourceLabel ? safeFileName(sourceLabel) : '' const baseName = [cleanAnime, cleanEpisode, cleanQuality, cleanSource] .filter(Boolean) .join(' - ') const apiExt = fileNameFromApi?.includes('.') ? fileNameFromApi.split('.').pop() : '' const finalExt = (apiExt || 'mp4').replace(/^\./, '') const finalFileName = safeFileName(baseName) + '.' + finalExt if (mime.startsWith('video/')) { await conn.sendMessage( chat, { video: buffer, mimetype: contentType || 'video/mp4', caption: `╭─❖ *تم التحميل بنجاح* ❖─╮\n│\n│ 📝 *الأنمي:* ${cleanAnime}\n│ 📺 *الحلقة:* ${cleanEpisode}\n│ ⚙️ *الجودة:* ${quality || 'غير معروف'}\n│ 🌐 *السيرفر:* ${sourceLabel || 'غير معروف'}\n│\n╰────────────────╯` }, { quoted } ) return } if (mime.startsWith('audio/')) { await conn.sendMessage( chat, { audio: buffer, mimetype: contentType || 'audio/mpeg', ptt: false }, { quoted } ) await conn.sendMessage( chat, { text: `╭─❖ *تم التحميل بنجاح* ❖─╮\n│\n│ 📝 *الأنمي:* ${cleanAnime}\n│ 📺 *الحلقة:* ${cleanEpisode}\n│ ⚙️ *الجودة:* ${quality || 'غير معروف'}\n│ 🌐 *السيرفر:* ${sourceLabel || 'غير معروف'}\n│\n╰────────────────╯` }, { quoted } ) return } await conn.sendMessage( chat, { document: buffer, mimetype: contentType || 'application/octet-stream', fileName: finalFileName }, { quoted } ) } async function sendSearchResults(conn, m, usedPrefix, query, results) { const token = setContext({ type: 'search', query, results }) const rows = results.slice(0, 20).map((item, index) => { const info = describeAnimeItem(item, index) return { header: info.header, title: info.title, description: info.description, id: `${usedPrefix}انمي search${SELECT_SEPARATOR}${token}${SELECT_SEPARATOR}${index}` } }) const posterUrl = decodeB64(results[0]?.poster) await sendSelectList(conn, m.chat, m, { title: `*🔎 نتائج البحث عن:* ${query}`, footer: 'اختر الأنمي المطلوب من القائمة:', posterUrl, sections: [ { title: '📥 نتائج البحث', highlight_label: `📥 ${rows.length} نتيجة`, rows } ] }) } async function sendEpisodesList(conn, m, usedPrefix, animeTitle, episodes, animeId, posterUrl) { const token = setContext({ type: 'episodes', animeTitle, animeId, episodes }) const rows = episodes.slice(0, 30).map((item, index) => { const info = describeEpisodeItem(item, index) return { header: info.header, title: info.title, description: info.description, id: `${usedPrefix}انمي episode${SELECT_SEPARATOR}${token}${SELECT_SEPARATOR}${index}` } }) await sendSelectList(conn, m.chat, m, { title: `*📺 الأنمي:* ${animeTitle}\n*🎬 عدد الحلقات:* ${episodes.length}`, footer: 'اختر الحلقة المطلوبة من القائمة:', posterUrl, sections: [ { title: '📥 الحلقات المتاحة', highlight_label: `📥 ${rows.length} حلقة`, rows } ] }) } async function sendLinksList(conn, m, usedPrefix, animeTitle, episodeTitle, episodeNumber, links, episodeId, posterUrl) { const token = setContext({ type: 'links', animeTitle, episodeTitle, episodeNumber, episodeId, links }) const rows = links.slice(0, 20).map((item, index) => { const info = describeLinkItem(item, index) return { header: info.header, title: info.title, description: info.description, id: `${usedPrefix}انمي link${SELECT_SEPARATOR}${token}${SELECT_SEPARATOR}${index}` } }) await sendSelectList(conn, m.chat, m, { title: `*📺 ${animeTitle}*\n*🧩 ${episodeTitle || `الحلقة ${episodeNumber}`}*`, footer: 'اختر الجودة أو السيرفر للتحميل:', posterUrl, sections: [ { title: '📥 روابط التحميل', highlight_label: `📥 ${rows.length} رابط`, rows } ] }) } const handler = async (m, { conn, args, usedPrefix, command }) => { const rawInput = args.join(' ').trim() if (!rawInput) { return conn.sendMessage( m.chat, { text: `╭─❖ *بحث وحلقات الأنمي* ❖─╮\n│\n│ أرسل اسم الأنمي للبحث:\n│ ${usedPrefix}${command} solo leveling\n│\n│ أو اختر من القوائم بعد البحث.\n│\n╰────────────────╯` }, { quoted: m } ) } const parts = rawInput.split(SELECT_SEPARATOR) const action = parts[0]?.trim()?.toLowerCase() try { if (action === 'search') { const token = parts[1] const index = parseInt(parts[2], 10) const ctx = getContext(token) if (!ctx || ctx.type !== 'search') { return conn.sendMessage( m.chat, { text: '❌ انتهت جلسة البحث، أعد إرسال اسم الأنمي.' }, { quoted: m } ) } const selected = ctx.results[index] if (!selected) { return conn.sendMessage( m.chat, { text: '❌ الاختيار غير موجود، أعد المحاولة.' }, { quoted: m } ) } await conn.sendMessage(m.chat, { text: '⏳ جاري جلب الحلقات...' }, { quoted: m }) const animeId = selected.id const animeTitle = cleanText(selected.title || 'بدون عنوان') const posterUrl = decodeB64(selected.poster) const { episodes } = await fetchEpisodes(animeId) await sendEpisodesList(conn, m, usedPrefix, animeTitle, episodes, animeId, posterUrl) return } if (action === 'episode') { const token = parts[1] const index = parseInt(parts[2], 10) const ctx = getContext(token) if (!ctx || ctx.type !== 'episodes') { return conn.sendMessage( m.chat, { text: '❌ انتهت جلسة الحلقات، أعد اختيار الأنمي.' }, { quoted: m } ) } const episode = ctx.episodes[index] if (!episode) { return conn.sendMessage( m.chat, { text: '❌ الحلقة غير موجودة، أعد المحاولة.' }, { quoted: m } ) } await conn.sendMessage(m.chat, { text: '⏳ جاري جلب روابط الحلقة...' }, { quoted: m }) const animeTitle = cleanText(ctx.animeTitle || 'بدون عنوان') const episodeTitle = cleanText(episode.title || `الحلقة ${episode.number || index + 1}`) const episodeNumber = episode.number || String(index + 1) const episodePoster = decodeB64(episode.thumb) const links = await fetchEpisodeLinks(episode.id) if (links.length === 1) { const link = links[0] await conn.sendMessage(m.chat, { text: '⏳ جاري تحميل الملف...' }, { quoted: m }) const resolved = await resolveDownloadUrl(link.link) await sendDownloadedMedia(conn, m.chat, m, { directUrl: resolved.downloadUrl, sourceLabel: link.server || resolved.provider, quality: link.quality || '', animeTitle, episodeTitle, episodeNumber, fileNameFromApi: resolved.fileName }) return } await sendLinksList(conn, m, usedPrefix, animeTitle, episodeTitle, episodeNumber, links, episode.id, episodePoster) return } if (action === 'link') { const token = parts[1] const index = parseInt(parts[2], 10) const ctx = getContext(token) if (!ctx || ctx.type !== 'links') { return conn.sendMessage( m.chat, { text: '❌ انتهت جلسة الروابط، أعد اختيار الحلقة.' }, { quoted: m } ) } const linkItem = ctx.links[index] if (!linkItem) { return conn.sendMessage( m.chat, { text: '❌ الرابط غير موجود، أعد المحاولة.' }, { quoted: m } ) } const animeTitle = cleanText(ctx.animeTitle || 'بدون عنوان') const episodeTitle = cleanText(ctx.episodeTitle || `الحلقة ${ctx.episodeNumber || ''}`.trim()) const episodeNumber = ctx.episodeNumber || '' await conn.sendMessage(m.chat, { text: '⏳ جاري تحميل الملف...' }, { quoted: m }) const resolved = await resolveDownloadUrl(linkItem.link) await sendDownloadedMedia(conn, m.chat, m, { directUrl: resolved.downloadUrl, sourceLabel: linkItem.server || resolved.provider, quality: linkItem.quality || '', animeTitle, episodeTitle, episodeNumber, fileNameFromApi: resolved.fileName }) return } if (isYouTubeUrl(rawInput)) { return conn.sendMessage( m.chat, { text: '❌ روابط يوتيوب غير مدعومة هنا.' }, { quoted: m } ) } await conn.sendMessage(m.chat, { text: '⏳ جاري البحث عن الأنمي...' }, { quoted: m }) const results = await fetchSearchResults(rawInput) await sendSearchResults(conn, m, usedPrefix, rawInput, results) } catch (e) { console.error('anime handler error:', e) const msg = e?.response?.data?.error || e?.message || 'حدث خطأ غير متوقع' await conn.sendMessage( m.chat, { text: `❌ ${msg}\n\nتأكد من الاسم وحاول مرة أخرى.` }, { quoted: m } ) } } handler.command = /^(انمي|anime|بحث_انمي|حلقات_انمي|تحميل_انمي)$/i handler.help = ['انمي <اسم>'] handler.tags = ['downloader'] export default handler