Skip to content

Instantly share code, notes, and snippets.

@weskerty
Last active January 2, 2025 08:23
Show Gist options
  • Save weskerty/ea2c9ff1f918991eb16ff7c19610bda5 to your computer and use it in GitHub Desktop.
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
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