anime
javascriptv1MITنُشر ١ أغسطس ٢٠٢٦· ♥ 0 · ⬇ 1 · 💬 0
بحث وتحميل انمي
// كود بحث وتحميل انمي بجودات
// 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
التعليقات (0)
سجّل الدخول للتعليق.