Last active
August 26, 2025 13:27
-
-
Save weskerty/2711c05a05e1dd41dcc6a00f078595cc to your computer and use it in GitHub Desktop.
Descarga Peliculas en Español
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| const axios = require('axios'); | |
| const cheerio = require('cheerio'); | |
| const { chromium } = require('playwright'); | |
| const fs = require('fs').promises; | |
| const path = require('path'); | |
| const os = require('os'); | |
| const { promisify } = require('util'); | |
| const { exec: execCallback } = require('child_process'); | |
| const { bot } = require('../lib'); | |
| require('dotenv').config(); | |
| const exec = promisify(execCallback); | |
| const searchResults = new Map(); | |
| const FILE_TYPES = { | |
| video: { | |
| extensions: new Set(['mp4', 'mkv', 'avi', 'webm', 'mov', 'flv', 'm4v']), | |
| mimetype: 'video/mp4', | |
| } | |
| }; | |
| function getFileDetails(filePath) { | |
| const ext = path.extname(filePath).slice(1).toLowerCase(); | |
| if (FILE_TYPES.video.extensions.has(ext)) { | |
| return { | |
| category: 'video', | |
| mimetype: FILE_TYPES.video.mimetype, | |
| }; | |
| } | |
| return { | |
| category: 'video', | |
| mimetype: FILE_TYPES.video.mimetype, | |
| }; | |
| } | |
| function sleep(ms) { | |
| return new Promise(resolve => setTimeout(resolve, ms)); | |
| } | |
| class CuevanaDownloader { | |
| constructor() { | |
| this.config = { | |
| tempDir: process.env.TEMP_DOWNLOAD_DIR || path.join(process.cwd(), 'tmp'), | |
| maxFileSize: (parseInt(process.env.MAX_UPLOAD, 10) * 1048576) || 1500000000, | |
| ytDlpPath: path.join(process.cwd(), 'media', 'bin'), | |
| cookies: process.env.COOKIES || null | |
| }; | |
| this.ytDlpCommand = null; | |
| this.ytDlpBinaries = new Map([ | |
| ['win32-x64', 'yt-dlp.exe'], | |
| ['win32-ia32', 'yt-dlp_x86.exe'], | |
| ['darwin', 'yt-dlp_macos'], | |
| ['linux-x64', 'yt-dlp_linux'], | |
| ['linux-arm64', 'yt-dlp_linux_aarch64'], | |
| ['linux-arm', 'yt-dlp_linux_armv7l'], | |
| ['default', 'yt-dlp'], | |
| ]); | |
| this.commonFlags = [ | |
| '--restrict-filenames', | |
| '--extractor-retries 3', | |
| '--fragment-retries 3', | |
| '--ignore-errors', | |
| '--no-abort-on-error' | |
| ].join(' '); | |
| } | |
| buildCookiesFlag() { | |
| const cookiesPath = path.join(this.config.ytDlpPath, 'yt-dlp.cookies.txt'); | |
| try { | |
| require('fs').accessSync(cookiesPath, require('fs').constants.F_OK); | |
| return `--cookies "${cookiesPath}"`; | |
| } catch { | |
| return this.config.cookies ? `--cookies "${this.config.cookies}"` : ''; | |
| } | |
| } | |
| async safeExecute(command, silentError = false) { | |
| try { | |
| const result = await exec(command); | |
| return result; | |
| } catch (error) { | |
| if (!silentError) { | |
| console.error(`Command: ${command}`); | |
| console.error(`Error: ${error.message}`); | |
| if (error.stdout) console.error(`Stdout: ${error.stdout}`); | |
| if (error.stderr) console.error(`Stderr: ${error.stderr}`); | |
| } | |
| throw error; | |
| } | |
| } | |
| async isYtDlpAvailable() { | |
| try { | |
| await exec('yt-dlp --version', { stdio: 'ignore' }); | |
| return true; | |
| } catch { | |
| return false; | |
| } | |
| } | |
| detectYtDlpBinaryName() { | |
| const platform = os.platform(); | |
| const arch = os.arch(); | |
| const key = `${platform}-${arch}`; | |
| return this.ytDlpBinaries.get(key) || this.ytDlpBinaries.get('default'); | |
| } | |
| async ensureDirectories() { | |
| await Promise.all([ | |
| fs.mkdir(this.config.tempDir, { recursive: true }), | |
| fs.mkdir(this.config.ytDlpPath, { recursive: true }), | |
| ]); | |
| } | |
| async detectYtDlpBinary() { | |
| if (this.ytDlpCommand) { | |
| return this.ytDlpCommand; | |
| } | |
| if (await this.isYtDlpAvailable()) { | |
| this.ytDlpCommand = 'nice -n 7 yt-dlp'; | |
| return this.ytDlpCommand; | |
| } | |
| const fileName = this.detectYtDlpBinaryName(); | |
| const filePath = path.join(this.config.ytDlpPath, fileName); | |
| try { | |
| await fs.access(filePath); | |
| this.ytDlpCommand = `nice -n 7 ${filePath}`; | |
| return this.ytDlpCommand; | |
| } catch { | |
| throw new Error('yt-dlp no encontrado. Usa el comando "dla" primero para descargarlo.'); | |
| } | |
| } | |
| async processDownloadedFile(message, filePath, customFileName) { | |
| const { mimetype, category } = getFileDetails(filePath); | |
| const fileBuffer = await fs.readFile(filePath); | |
| await message.send( | |
| fileBuffer, | |
| { fileName: customFileName, mimetype, quoted: message.data }, | |
| category | |
| ); | |
| await fs.unlink(filePath).catch(() => {}); | |
| } | |
| sanitizeFilename(filename) { | |
| return filename.replace(/[<>:"/\\|?*]/g, '_').replace(/\s+/g, '_'); | |
| } | |
| async downloadM3U8(message, m3u8URLs, movieTitle, statusMsg) { | |
| const ytDlpPath = await this.detectYtDlpBinary(); | |
| const sessionId = `cuevana_${Date.now()}`; | |
| const outputDir = path.join(this.config.tempDir, sessionId); | |
| const cookiesFlag = this.buildCookiesFlag(); | |
| const sanitizedTitle = this.sanitizeFilename(movieTitle); | |
| await this.ensureDirectories(); | |
| await fs.mkdir(outputDir, { recursive: true }); | |
| await sleep(1000); | |
| await message.send({ key: statusMsg.key, text: `✅ M3U8 encontrados (${m3u8URLs.length}). Iniciando descarga...` }, {}, 'edit'); | |
| await sleep(1000); | |
| await message.send({ key: statusMsg.key, text: `⬇️ Descargando ${movieTitle}` }, {}, 'edit'); | |
| let downloadSuccess = false; | |
| for (const url of m3u8URLs) { | |
| if (downloadSuccess) break; | |
| try { | |
| const outputTemplate = path.join(outputDir, `${sanitizedTitle}.%(ext)s`); | |
| const command = [ | |
| ytDlpPath, | |
| '-f worst', | |
| '--downloader ffmpeg', | |
| '--hls-use-mpegts', | |
| `--max-filesize ${this.config.maxFileSize}`, | |
| this.commonFlags, | |
| cookiesFlag, | |
| `-o "${outputTemplate}"`, | |
| `"${url}"` | |
| ].filter(Boolean).join(' '); | |
| await this.safeExecute(command); | |
| const files = await fs.readdir(outputDir); | |
| if (files.length > 0) { | |
| for (const file of files) { | |
| try { | |
| const fileExtension = path.extname(file); | |
| const customFileName = `${sanitizedTitle}${fileExtension}`; | |
| await this.processDownloadedFile( | |
| message, | |
| path.join(outputDir, file), | |
| customFileName | |
| ); | |
| downloadSuccess = true; | |
| } catch (processError) { | |
| console.error(`Failed to process file ${file}: ${processError.message}`); | |
| } | |
| } | |
| } | |
| } catch (error) { | |
| console.error(`Error downloading ${url}: ${error.message}`); | |
| } | |
| } | |
| await fs.rm(outputDir, { recursive: true, force: true }).catch(() => {}); | |
| if (!downloadSuccess) { | |
| await message.send({ key: statusMsg.key, text: '❌ No se pudo descargar ningún enlace M3U8' }, {}, 'edit'); | |
| } | |
| } | |
| } | |
| class M3U8Extractor { | |
| constructor() { | |
| this.m3u8URLs = new Set(); | |
| this.config = { | |
| chromiumDataDir: process.env.CHROMIUM_DATA_DIR || '', | |
| chromiumPath: process.env.CHROMIUM_PATH || 'chromium', | |
| userAgent: process.env.CHROMIUM_USERAGENT || 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' | |
| }; | |
| } | |
| async extractM3U8(url, options = {}) { | |
| const { timeout = 60000, waitAfterClick = 8000 } = options; | |
| this.m3u8URLs.clear(); | |
| if (!url) throw new Error('URL requerida'); | |
| const browser = await chromium.launchPersistentContext(this.config.chromiumDataDir, { | |
| headless: true, | |
| executablePath: this.config.chromiumPath, | |
| viewport: { width: 1366, height: 768 }, | |
| args: [ | |
| '--no-sandbox', | |
| '--disable-gpu', | |
| '--disable-software-rasterizer', | |
| '--disable-dev-shm-usage', | |
| '--allow-outdated-plugins', | |
| '--disable-logging', | |
| '--disable-breakpad', | |
| '--disable-encryption', | |
| '--disable-machine-id', | |
| '--force-dark-mode', | |
| '--thorium-2024', | |
| '--disable-thorium-icons', | |
| '--remove-tabsearch-button', | |
| '--classic-omnibox', | |
| '--rectangular-tabs', | |
| '--allow-insecure-downloads', | |
| '--allow-insecure-localhost', | |
| '--enable-parallel-downloading', | |
| '--enable-ftp', | |
| '--disable-pull-to-refresh', | |
| '--disable-scrollable-tabstrip', | |
| '--disable-ntp-realbox-cr23-all', | |
| '--disable-ntp-realbox-cr23-consistent-row-height', | |
| '--disable-ntp-realbox-cr23-expanded-state-icons', | |
| '--disable-ntp-realbox-cr23-expanded-state-layout', | |
| '--disable-ntp-realbox-cr23-hover-fill-shape', | |
| '--disable-ntp-realbox-cr23-theming', | |
| '--disable-read-anything-read-aloud', | |
| '--disable-read-anything-with-screen2x', | |
| '--disable-read-anything-with-algorithm', | |
| '--disable-read-anything-images-via-algorithm', | |
| '--disable-read-anything-local-side-panel', | |
| '--disable-chrome-labs', | |
| '--disable-enable-tab-audio-muting', | |
| '--disable-link-preview', | |
| '--autoplay-policy=user-gesture-required', | |
| '--tab-hover-cards=none', | |
| '--close-window-with-last-tab=never', | |
| '--show-avatar-button=always', | |
| '--flag-switches-begin', | |
| '--enable-unsafe-webgpu', | |
| '--ignore-gpu-blocklist', | |
| '--pull-to-refresh=0', | |
| '--revert-from-portable', | |
| '--enable-smooth-scrolling', | |
| '--enable-features=FtpProtocol,ParallelDownloading,Thorium2024,WebContentsForceDark:inversion_method/cielab_based/image_behavior/none/foreground_lightness_threshold/150/background_lightness_threshold/205,Windows11MicaTitlebar', | |
| '--disable-features=ChromeLabs,EnableTabMuting,LinkPreview,NtpRealboxCr23Theming,PdfUseSkiaRenderer,ReadAnythingImagesViaAlgorithm,ReadAnythingReadAloud,ReadAnythingWithAlgorithm,ReadAnythingWithScreen2x,ScrollableTabStrip,SkiaGraphite,UseDMSAAForTiles,TranslateOpenSettings', | |
| '--flag-switches-end', | |
| '--force-disable-tab-outlines', | |
| '--disable-blink-features=AutomationControlled', | |
| '--no-first-run', | |
| '--disable-default-apps', | |
| '--disable-popup-blocking', | |
| '--no-sandbox', | |
| '--disable-setuid-sandbox', | |
| '--disable-dev-shm-usage', | |
| '--headless', | |
| '--disable-gpu' | |
| ] | |
| }); | |
| const page = await browser.newPage(); | |
| await page.addInitScript(() => { | |
| Object.defineProperty(HTMLMediaElement.prototype, 'play', { | |
| writable: true, | |
| value: function() { return Promise.resolve(); } | |
| }); | |
| window.isSpanishContent = function(text) { | |
| if (!text) return false; | |
| const lowerText = text.toLowerCase(); | |
| return lowerText.includes('español') || lowerText.includes('latino'); | |
| }; | |
| window.prioritizeSpanish = function(a, b) { | |
| const aLower = a.toLowerCase(); | |
| const bLower = b.toLowerCase(); | |
| if (aLower.includes('español latino') && !bLower.includes('español latino')) return -1; | |
| if (!aLower.includes('español latino') && bLower.includes('español latino')) return 1; | |
| if (aLower.includes('latino') && !bLower.includes('latino')) return -1; | |
| if (!aLower.includes('latino') && bLower.includes('latino')) return 1; | |
| if (aLower.includes('español') && !bLower.includes('español')) return -1; | |
| if (!aLower.includes('español') && bLower.includes('español')) return 1; | |
| return 0; | |
| }; | |
| }); | |
| page.on('request', request => { | |
| const url = request.url(); | |
| if (url.includes('.m3u8') || url.includes('m3u8') || url.includes('playlist') || url.includes('master')) { | |
| this.m3u8URLs.add(url); | |
| } | |
| }); | |
| page.on('response', response => { | |
| const url = response.url(); | |
| const contentType = response.headers()['content-type'] || ''; | |
| if (url.includes('.m3u8') || contentType.includes('application/vnd.apple.mpegurl') || contentType.includes('application/x-mpegURL') || (url.includes('playlist') && contentType.includes('text/plain'))) { | |
| this.m3u8URLs.add(url); | |
| } | |
| }); | |
| try { | |
| await page.goto(url, { waitUntil: 'domcontentloaded', timeout }); | |
| await this.scrollToLoadLazy(page); | |
| await page.waitForTimeout(3000); | |
| await this.detectAndExtract(page, waitAfterClick); | |
| } catch (error) { | |
| throw new Error(`Error durante extracción: ${error.message}`); | |
| } finally { | |
| await browser.close(); | |
| } | |
| return Array.from(this.m3u8URLs); | |
| } | |
| async detectAndExtract(page, waitAfterClick) { | |
| const strategies = [ | |
| () => this.extractFromTabs(page, waitAfterClick), | |
| () => this.extractFromDropdown(page, waitAfterClick), | |
| () => this.extractFromOriginal(page, waitAfterClick), | |
| () => this.extractFromIframes(page, waitAfterClick) | |
| ]; | |
| for (const strategy of strategies) { | |
| const initialCount = this.m3u8URLs.size; | |
| await strategy(); | |
| if (this.m3u8URLs.size > initialCount) break; | |
| } | |
| if (this.m3u8URLs.size === 0) { | |
| await this.searchM3U8InDOM(page); | |
| } | |
| } | |
| async extractFromTabs(page, waitAfterClick) { | |
| const tabs = await page.evaluate(() => { | |
| const tabList = document.querySelector('.TbVideoNv'); | |
| if (!tabList) return []; | |
| const items = tabList.querySelectorAll('li[data-url][data-name]'); | |
| return Array.from(items) | |
| .filter(item => window.isSpanishContent(item.getAttribute('data-name'))) | |
| .map((item, index) => ({ | |
| index, | |
| url: item.getAttribute('data-url'), | |
| name: item.getAttribute('data-name') | |
| })) | |
| .sort((a, b) => window.prioritizeSpanish(a.name, b.name)); | |
| }); | |
| for (const tab of tabs) { | |
| await page.evaluate((index) => { | |
| const tabList = document.querySelector('.TbVideoNv'); | |
| const items = tabList.querySelectorAll('li[data-url][data-name]'); | |
| const spanishItems = Array.from(items).filter(item => | |
| window.isSpanishContent(item.getAttribute('data-name')) | |
| ); | |
| if (spanishItems[index]) spanishItems[index].click(); | |
| }, tab.index); | |
| await page.waitForTimeout(waitAfterClick); | |
| await this.extractFromIframes(page, waitAfterClick); | |
| } | |
| } | |
| async extractFromDropdown(page, waitAfterClick) { | |
| const hasDropdown = await page.evaluate(() => { | |
| return document.querySelector('.repro_dropdown') !== null; | |
| }); | |
| if (!hasDropdown) return; | |
| await page.evaluate(() => { | |
| const dropdown = document.querySelector('.repro_dropdown'); | |
| if (dropdown) { | |
| const btn = dropdown.querySelector('.repro_dropdown_btn'); | |
| const content = dropdown.querySelector('.repro_dropdown_content'); | |
| if (btn && content) { | |
| if (content.style.display === 'none' || !content.classList.contains('repro_show')) { | |
| btn.click(); | |
| } | |
| } | |
| } | |
| }); | |
| await page.waitForTimeout(2000); | |
| const servers = await page.evaluate(() => { | |
| const items = document.querySelectorAll('#episode-options li span[data-id]'); | |
| return Array.from(items).map((item, index) => ({ | |
| index, | |
| title: item.getAttribute('title'), | |
| id: item.getAttribute('data-id') | |
| })); | |
| }); | |
| for (const server of servers) { | |
| await page.evaluate((id) => { | |
| const item = document.querySelector(`#episode-options li span[data-id="${id}"]`); | |
| if (item) item.click(); | |
| }, server.id); | |
| await page.waitForTimeout(waitAfterClick); | |
| await this.extractFromIframes(page, waitAfterClick); | |
| } | |
| } | |
| async extractFromOriginal(page, waitAfterClick) { | |
| const expanded = await page.evaluate(() => { | |
| const spanishSection = Array.from(document.querySelectorAll('li.open_submenu')).find(li => { | |
| const spanElement = li.querySelector('._1R6bW_0 span'); | |
| return spanElement && window.isSpanishContent(spanElement.textContent); | |
| }); | |
| if (spanishSection) { | |
| const menuButton = spanishSection.querySelector('._3CT5n_0'); | |
| if (menuButton) { | |
| menuButton.click(); | |
| return true; | |
| } | |
| } | |
| return false; | |
| }); | |
| if (!expanded) return; | |
| await page.waitForTimeout(2000); | |
| const servers = await page.evaluate(() => { | |
| const spanishSection = Array.from(document.querySelectorAll('li.open_submenu')).find(li => { | |
| const spanElement = li.querySelector('._1R6bW_0 span'); | |
| return spanElement && window.isSpanishContent(spanElement.textContent); | |
| }); | |
| if (spanishSection) { | |
| const subMenu = spanishSection.querySelector('.sub-tab-lang'); | |
| if (subMenu) { | |
| const serverItems = subMenu.querySelectorAll('li.clili[data-tr]'); | |
| return Array.from(serverItems).map((item, index) => ({ | |
| index, | |
| datatr: item.getAttribute('data-tr'), | |
| name: item.textContent.trim() | |
| })); | |
| } | |
| } | |
| return []; | |
| }); | |
| for (const server of servers) { | |
| await page.evaluate((serverIndex) => { | |
| const spanishSection = Array.from(document.querySelectorAll('li.open_submenu')).find(li => { | |
| const spanElement = li.querySelector('._1R6bW_0 span'); | |
| return spanElement && window.isSpanishContent(spanElement.textContent); | |
| }); | |
| if (spanishSection) { | |
| const subMenu = spanishSection.querySelector('.sub-tab-lang'); | |
| if (subMenu) { | |
| const serverItems = subMenu.querySelectorAll('li.clili[data-tr]'); | |
| if (serverItems[serverIndex]) { | |
| serverItems[serverIndex].click(); | |
| } | |
| } | |
| } | |
| }, server.index); | |
| await page.waitForTimeout(waitAfterClick); | |
| await this.extractFromIframes(page, waitAfterClick); | |
| } | |
| } | |
| async extractFromIframes(page, waitAfterClick) { | |
| const iframes = await page.$$('iframe'); | |
| for (const iframe of iframes) { | |
| try { | |
| const isVisible = await iframe.isVisible(); | |
| if (!isVisible) continue; | |
| const box = await iframe.boundingBox(); | |
| if (box && box.width > 50 && box.height > 50) { | |
| const centerX = box.x + box.width / 2; | |
| const centerY = box.y + box.height / 2; | |
| await page.mouse.click(centerX, centerY); | |
| await page.waitForTimeout(1000); | |
| await iframe.click({ force: true }); | |
| await page.waitForTimeout(waitAfterClick); | |
| } | |
| } catch (error) { | |
| continue; | |
| } | |
| } | |
| } | |
| async scrollToLoadLazy(page) { | |
| await page.evaluate(() => { | |
| return new Promise((resolve) => { | |
| let totalHeight = 0; | |
| const distance = 100; | |
| const timer = setInterval(() => { | |
| const scrollHeight = document.body.scrollHeight; | |
| window.scrollBy(0, distance); | |
| totalHeight += distance; | |
| if(totalHeight >= scrollHeight){ | |
| clearInterval(timer); | |
| resolve(); | |
| } | |
| }, 100); | |
| }); | |
| }); | |
| await page.waitForTimeout(2000); | |
| await page.evaluate(() => { | |
| window.scrollTo({ top: 0, behavior: 'smooth' }); | |
| }); | |
| await page.waitForTimeout(1000); | |
| } | |
| async searchM3U8InDOM(page) { | |
| const m3u8InDOM = await page.evaluate(() => { | |
| const urls = []; | |
| const regex = /https?:\/\/[^\s"']+\.m3u8[^\s"']*/g; | |
| const htmlContent = document.documentElement.outerHTML; | |
| const matches = htmlContent.match(regex); | |
| if (matches) urls.push(...matches); | |
| for (let key in window) { | |
| try { | |
| const value = window[key]; | |
| if (typeof value === 'string' && value.includes('.m3u8')) { | |
| const urlMatches = value.match(regex); | |
| if (urlMatches) urls.push(...urlMatches); | |
| } | |
| } catch (e) {} | |
| } | |
| return [...new Set(urls)]; | |
| }); | |
| m3u8InDOM.forEach(url => this.m3u8URLs.add(url)); | |
| } | |
| } | |
| class CuevanaSearcher { | |
| constructor() { | |
| this.config = { | |
| maxResults: 10, | |
| baseUrl: 'https://wwv.cuevana3.eu', | |
| userAgent: process.env.CHROMIUM_USERAGENT || 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', | |
| messages: { | |
| usage: 'Uso: cuevana <nombre de película>\nEjemplo: cuevana avengers\n\nUso: cuevana dd <número>\nEjemplo: cuevana dd 1', | |
| searching: '🔍 Buscando películas...', | |
| noResults: 'No se encontraron resultados.', | |
| error: 'Error al procesar.' | |
| } | |
| }; | |
| this.headers = { | |
| 'User-Agent': this.config.userAgent | |
| }; | |
| } | |
| async makeRequest(url) { | |
| return await axios.get(url, { headers: this.headers }); | |
| } | |
| async searchMovies(query) { | |
| try { | |
| const response = await this.makeRequest(`${this.config.baseUrl}/search?q=${encodeURIComponent(query)}`); | |
| const $ = cheerio.load(response.data); | |
| const results = []; | |
| $('.MovieList .TPostMv').each((_, element) => { | |
| const $element = $(element); | |
| const title = $element.find('.TPostMv .Title').first().text().trim(); | |
| const link = this.config.baseUrl + $element.find('a').attr('href'); | |
| if (title && link) { | |
| results.push({ title, link }); | |
| } | |
| }); | |
| return results; | |
| } catch (error) { | |
| console.error('Error en Cuevana Plugin Buscar:', error); | |
| throw error; | |
| } | |
| } | |
| async getMovieInfo(url) { | |
| try { | |
| const response = await this.makeRequest(url); | |
| const $ = cheerio.load(response.data); | |
| const title = $('h1.Title').text().trim(); | |
| const sinopsis = $('.Description p').text().trim(); | |
| let genero = ''; | |
| $('.TPost .InfoList li').each((i, el) => { | |
| const label = $(el).find('strong').text().trim(); | |
| const value = $(el).text().replace(label, '').trim(); | |
| if (label.toLowerCase().includes('género') || label.toLowerCase().includes('genero')) { | |
| genero = value; | |
| } | |
| }); | |
| return { | |
| title, | |
| genero, | |
| sinopsis, | |
| onlineLink: url | |
| }; | |
| } catch (error) { | |
| console.error('Error en Cuevana Plugin:', error); | |
| throw error; | |
| } | |
| } | |
| async sendMovieInfo(message, movieInfo, index) { | |
| try { | |
| let infoMessage = `🎬 *${index + 1} ${movieInfo.title}*\n`; | |
| if (movieInfo.genero) { | |
| infoMessage += `> ${movieInfo.genero}\n`; | |
| } | |
| if (movieInfo.sinopsis) { | |
| infoMessage += `📖 ${movieInfo.sinopsis}\n`; | |
| } | |
| infoMessage += `🌐 ${movieInfo.onlineLink}`; | |
| await message.send(infoMessage, { quoted: message.data }); | |
| } catch (error) { | |
| console.error('Error en Cuevana Plugin:', error); | |
| await message.send(`❌ Error al procesar: ${movieInfo.title}`, { quoted: message.data }); | |
| } | |
| } | |
| delay(ms) { | |
| return new Promise(resolve => setTimeout(resolve, ms)); | |
| } | |
| } | |
| const cuevanaSearcher = new CuevanaSearcher(); | |
| const m3u8Extractor = new M3U8Extractor(); | |
| const cuevanaDownloader = new CuevanaDownloader(); | |
| bot( | |
| { | |
| pattern: 'cuevana ?(.*)', | |
| fromMe: true, | |
| desc: 'Buscar y descargar películas de Cuevana3', | |
| type: 'search', | |
| }, | |
| async (message, match) => { | |
| const input = (match || '').trim(); | |
| const chatId = message.jid; | |
| if (!input) { | |
| return await message.send(cuevanaSearcher.config.messages.usage, { quoted: message.data }); | |
| } | |
| if (input.startsWith('dd ')) { | |
| const resultIndex = parseInt(input.replace('dd ', '')) - 1; | |
| if (!searchResults.has(chatId)) { | |
| return await message.send('❌ No hay resultados de búsqueda. Usa *cuevana [búsqueda]* primero.', { quoted: message.data }); | |
| } | |
| const results = searchResults.get(chatId); | |
| if (resultIndex < 0 || resultIndex >= results.length || !results[resultIndex]) { | |
| return await message.send(`❌ Número inválido o película no disponible. Selecciona entre 1 y ${results.length}.`, { quoted: message.data }); | |
| } | |
| const selectedMovie = results[resultIndex]; | |
| try { | |
| const statusMsg = await message.send(`🎬 Seleccionado: *${selectedMovie.title}*\n⏳ Extrayendo enlaces M3U8...`, { quoted: message.data }); | |
| const m3u8URLs = await m3u8Extractor.extractM3U8(selectedMovie.onlineLink); | |
| if (m3u8URLs.length > 0) { | |
| await cuevanaDownloader.downloadM3U8(message, m3u8URLs, selectedMovie.title, statusMsg); | |
| } else { | |
| await message.send({ key: statusMsg.key, text: '❌ No se encontraron enlaces M3U8 en esta película.' }, {}, 'edit'); | |
| } | |
| } catch (error) { | |
| console.error('Error extrayendo M3U8:', error); | |
| await message.send(`❌ Error: ${error.message}`, { quoted: message.data }); | |
| } | |
| return; | |
| } | |
| const query = input; | |
| try { | |
| await message.send(cuevanaSearcher.config.messages.searching, { quoted: message.data }); | |
| const searchResultsArray = await cuevanaSearcher.searchMovies(query); | |
| if (!searchResultsArray || searchResultsArray.length === 0) { | |
| return await message.send(cuevanaSearcher.config.messages.noResults, { quoted: message.data }); | |
| } | |
| const limitedResults = searchResultsArray.slice(0, cuevanaSearcher.config.maxResults); | |
| const moviesWithInfo = []; | |
| for (let i = 0; i < limitedResults.length; i++) { | |
| const movie = limitedResults[i]; | |
| try { | |
| const movieInfo = await cuevanaSearcher.getMovieInfo(movie.link); | |
| moviesWithInfo.push(movieInfo); | |
| await cuevanaSearcher.sendMovieInfo(message, movieInfo, i); | |
| if (i < limitedResults.length - 1) { | |
| await cuevanaSearcher.delay(1000); | |
| } | |
| } catch (error) { | |
| console.error(`Error procesando película ${i + 1}:`, error); | |
| await message.send(`❌ Error al obtener información de: ${movie.title}`, { quoted: message.data }); | |
| moviesWithInfo.push(null); | |
| } | |
| } | |
| searchResults.set(chatId, moviesWithInfo); | |
| await message.send('💡 Usa: *cuevana dd [número]* para extraer y descargar M3U8', { quoted: message.data }); | |
| } catch (error) { | |
| console.error('Error en Cuevana Plugin:', error); | |
| await message.send(cuevanaSearcher.config.messages.error, { quoted: message.data }); | |
| } | |
| } | |
| ); | |
| module.exports = { cuevanaDownloader, m3u8Extractor, cuevanaSearcher }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment