Last active
January 2, 2025 08:23
-
-
Save weskerty/ea2c9ff1f918991eb16ff7c19610bda5 to your computer and use it in GitHub Desktop.
WhatsApp Web Browser. Need Linux Plugin to: .linux npm install playwright@latest --no-save --force
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 fs = require('fs').promises; | |
const path = require('path'); | |
const { chromium } = require('playwright'); | |
const { bot, isUrl } = require('../lib'); | |
require('dotenv').config(); | |
class WebDownloader { | |
constructor() { | |
this.config = { | |
tempDir: process.env.TEMP_DOWNLOAD_DIR || path.join(process.cwd(), 'tmp'), | |
maxImageSize: parseInt(process.env.MAX_IMAGE_SIZE, 10) || 11240, | |
maxImages: parseInt(process.env.MAX_IMAGES, 10) || 10, | |
browserTimeout: parseInt(process.env.BROWSER_TIMEOUT, 10) || 99000, | |
browserWaitTime: parseInt(process.env.BROWSER_WAIT_TIME, 10) || 9000, | |
userAgent: process.env.USER_AGENT || 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', | |
chromiumPath: process.env.CHROMIUM_PATH || '' | |
}; | |
this.browserInstance = null; | |
this.activeRequests = 0; | |
} | |
generateSafeFileName(originalName) { | |
const ext = path.extname(originalName); | |
const timestamp = Date.now(); | |
return `web_${timestamp}${ext}`; | |
} | |
async ensureDirectories(sessionId) { | |
const sessionDir = path.join(this.config.tempDir, sessionId); | |
await fs.mkdir(sessionDir, { recursive: true }); | |
return sessionDir; | |
} | |
async initBrowser() { | |
if (!this.browserInstance) { | |
const launchOptions = { | |
headless: true, | |
args: [ | |
'--disable-blink-features=AutomationControlled', | |
'--disable-web-security', | |
'--disable-features=IsolateOrigins', | |
'--disable-site-isolation-trials', | |
'--disable-features=BlockInsecurePrivateNetworkRequests', | |
'--proxy-server=\'direct://\'', | |
'--proxy-bypass-list=*', | |
'--headless', | |
'--hide-scrollbars', | |
'--mute-audio', | |
'--disable-logging', | |
'--disable-infobars', | |
'--disable-breakpad', | |
'--disable-gl-drawing-for-tests', | |
'--disable-canvas-aa', | |
'--disable-2d-canvas-clip-aa', | |
'--no-sandbox' | |
] | |
}; | |
if (this.config.chromiumPath) { | |
launchOptions.executablePath = this.config.chromiumPath; | |
} | |
try { | |
this.browserInstance = await chromium.launch(launchOptions); | |
} catch (error) { | |
throw new Error(`Browser initialization failed: ${error.message}`); | |
} | |
} | |
return this.browserInstance; | |
} | |
async closeBrowserIfIdle() { | |
if (this.activeRequests === 0 && this.browserInstance) { | |
await this.browserInstance.close().catch(() => {}); | |
this.browserInstance = null; | |
} | |
} | |
async processDownloadedFile(message, filePath, type) { | |
const safeFileName = this.generateSafeFileName(`web.${type}`); | |
const mimeTypes = { | |
'pdf': 'application/pdf', | |
'mhtml': 'text/html', | |
'jpg': 'image/jpeg', | |
'png': 'image/png' | |
}; | |
try { | |
await message.send( | |
await fs.readFile(filePath), | |
{ | |
fileName: safeFileName, | |
mimetype: mimeTypes[type], | |
quoted: message.data | |
}, | |
type === 'pdf' ? 'document' : (type === 'mhtml' ? 'document' : 'image') | |
); | |
} finally { | |
await fs.unlink(filePath).catch(() => {}); | |
} | |
} | |
async downloadAsMHTML(message, url) { | |
this.activeRequests++; | |
const sessionId = `web_${Date.now()}`; | |
const outputDir = await this.ensureDirectories(sessionId); | |
try { | |
const browser = await this.initBrowser(); | |
const context = await browser.newContext({ userAgent: this.config.userAgent }); | |
const page = await context.newPage(); | |
await Promise.all([ | |
page.goto(url, { | |
timeout: this.config.browserTimeout, | |
waitUntil: 'networkidle' | |
}), | |
new Promise(resolve => setTimeout(resolve, this.config.browserWaitTime)) | |
]); | |
const client = await context.newCDPSession(page); | |
const mhtmlData = (await client.send('Page.captureSnapshot', { format: 'mhtml' })).data; | |
const filePath = path.join(outputDir, this.generateSafeFileName('web.mhtml')); | |
await fs.writeFile(filePath, mhtmlData); | |
await this.processDownloadedFile(message, filePath, 'mhtml'); | |
await page.close(); | |
await context.close(); | |
} catch (error) { | |
await message.send(`❌ Error al capturar MHTML: ${error.message}`, { quoted: message.data }); | |
} finally { | |
this.activeRequests--; | |
await this.closeBrowserIfIdle(); | |
await fs.rmdir(outputDir, { recursive: true }).catch(() => {}); | |
} | |
} | |
async downloadAsPDF(message, url) { | |
this.activeRequests++; | |
const sessionId = `web_${Date.now()}`; | |
const outputDir = await this.ensureDirectories(sessionId); | |
try { | |
const browser = await this.initBrowser(); | |
const context = await browser.newContext({ userAgent: this.config.userAgent }); | |
const page = await context.newPage(); | |
await Promise.all([ | |
page.goto(url, { | |
timeout: this.config.browserTimeout, | |
waitUntil: 'networkidle' | |
}), | |
new Promise(resolve => setTimeout(resolve, this.config.browserWaitTime)) | |
]); | |
const filePath = path.join(outputDir, this.generateSafeFileName('web.pdf')); | |
await page.pdf({ | |
path: filePath, | |
format: 'A4', | |
printBackground: true | |
}); | |
await this.processDownloadedFile(message, filePath, 'pdf'); | |
await page.close(); | |
await context.close(); | |
} catch (error) { | |
await message.send(`❌ Error al generar PDF: ${error.message}`, { quoted: message.data }); | |
} finally { | |
this.activeRequests--; | |
await this.closeBrowserIfIdle(); | |
await fs.rmdir(outputDir, { recursive: true }).catch(() => {}); | |
} | |
} | |
async downloadImages(message, url) { | |
this.activeRequests++; | |
const sessionId = `web_${Date.now()}`; | |
const outputDir = await this.ensureDirectories(sessionId); | |
try { | |
const browser = await this.initBrowser(); | |
const context = await browser.newContext({ userAgent: this.config.userAgent }); | |
const page = await context.newPage(); | |
await Promise.all([ | |
page.goto(url, { | |
timeout: this.config.browserTimeout, | |
waitUntil: 'networkidle' | |
}), | |
new Promise(resolve => setTimeout(resolve, this.config.browserWaitTime)) | |
]); | |
const images = await page.evaluate(() => { | |
return Array.from(document.images).map(img => ({ | |
src: img.src, | |
size: img.naturalWidth * img.naturalHeight | |
})); | |
}); | |
const validImages = images | |
.filter(img => img.size > this.config.maxImageSize) | |
.map(img => img.src) | |
.slice(0, this.config.maxImages); | |
if (validImages.length === 0) { | |
await message.send('❌ No se encontraron imágenes mayores a 10KB', { quoted: message.data }); | |
return; | |
} | |
const fetch = (await import('node-fetch')).default; | |
for (const imageUrl of validImages) { | |
try { | |
const response = await fetch(imageUrl); | |
if (!response.ok) continue; | |
const buffer = await response.buffer(); | |
const filePath = path.join(outputDir, this.generateSafeFileName('image.jpg')); | |
await fs.writeFile(filePath, buffer); | |
await this.processDownloadedFile(message, filePath, 'jpg'); | |
} catch (error) { | |
console.error(`Error downloading image ${imageUrl}:`, error); | |
} | |
} | |
await page.close(); | |
await context.close(); | |
} catch (error) { | |
await message.send(`❌ Error al descargar imágenes: ${error.message}`, { quoted: message.data }); | |
} finally { | |
this.activeRequests--; | |
await this.closeBrowserIfIdle(); | |
await fs.rmdir(outputDir, { recursive: true }).catch(() => {}); | |
} | |
} | |
} | |
const webDownloader = new WebDownloader(); | |
bot( | |
{ | |
pattern: 'web ?(.*)', | |
fromMe: true, | |
desc: 'Descarga páginas web en formatos MHTML, PDF o imágenes.', | |
type: 'downloads', | |
}, | |
async (message, match) => { | |
const args = match.split(/\s+/); | |
const url = args.find(arg => isUrl(arg)); | |
const command = args[0].toLowerCase(); | |
if (!url) { | |
await message.send('❌ Proporciona una URL válida', { quoted: message.data }); | |
return; | |
} | |
try { | |
if (command === 'img') { | |
await webDownloader.downloadImages(message, url); | |
} else if (command === 'pdf') { | |
await webDownloader.downloadAsPDF(message, url); | |
} else { | |
await webDownloader.downloadAsMHTML(message, url); | |
} | |
} catch (error) { | |
await message.send(`❌ Error: ${error.message}`, { quoted: message.data }); | |
} | |
} | |
); | |
module.exports = { webDownloader }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment