const express = require('express'); const { exec, spawn } = require('child_process'); const path = require('path'); const fs = require('fs'); const axios = require('axios'); const app = express(); app.use(express.json()); class WgetCloner { constructor() { this.baseOutputDir = path.join(__dirname, '..', 'cloned-sites'); this.wgetPath = path.join(__dirname, 'wget.exe'); this.ensureDirectoryExists(this.baseOutputDir); } ensureDirectoryExists(dirPath) { if (!fs.existsSync(dirPath)) { fs.mkdirSync(dirPath, { recursive: true }); } } sanitizeFilename(filename) { return filename.replace(/[<>:"/\\|?*]/g, '_').replace(/\s+/g, '_'); } async checkWget() { return new Promise((resolve) => { exec('where wget', (error, stdout) => { if (error || !stdout) { resolve(false); } else { resolve(true); } }); }); } async downloadWget() { console.log('📥 Baixando wget para Windows...'); try { // URL do wget para Windows (versão portável) const wgetUrl = 'https://eternallybored.org/misc/wget/1.21.4/64/wget.exe'; console.log('🔗 Conectando ao servidor...'); const response = await axios.get(wgetUrl, { responseType: 'arraybuffer', timeout: 120000, maxContentLength: 50 * 1024 * 1024, // 50MB onDownloadProgress: (progressEvent) => { const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total); process.stdout.write(`\r📦 Baixando: ${percentCompleted}%`); } }); console.log('\n💾 Salvando arquivo...'); fs.writeFileSync(this.wgetPath, response.data); console.log('✅ wget baixado com sucesso!'); return true; } catch (error) { console.error('\n❌ Erro ao baixar wget:', error.message); console.log('💡 Você pode baixar manualmente de: https://eternallybored.org/misc/wget/'); return false; } } async cloneWebsite(url) { console.log(`\n${'='.repeat(60)}`); console.log(`🚀 CLONAGEM PROFISSIONAL COM WGET`); console.log(`${'='.repeat(60)}`); console.log(`🌐 URL: ${url}\n`); // Sempre usar o wget.exe local primeiro let wgetCommand = '/bin/wget'; // Linux system wget // Verificar se o wget.exe local existe if (false) { // bypass wget.exe check console.log('⚠️ wget.exe local não encontrado'); // Tentar baixar const downloaded = await this.downloadWget(); if (!downloaded) { // Tentar usar wget do sistema como fallback const hasWget = await this.checkWget(); if (hasWget) { console.log('✅ Usando wget do sistema'); wgetCommand = 'wget'; } else { throw new Error('Não foi possível baixar o wget. Instale manualmente: https://eternallybored.org/misc/wget/'); } } } else { console.log('✅ Usando wget.exe local'); } // Criar diretório para o site const urlObj = new URL(url); const siteName = this.sanitizeFilename(urlObj.hostname); const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const siteDir = path.join(this.baseOutputDir, `${siteName}_${timestamp}`); this.ensureDirectoryExists(siteDir); console.log(`📁 Salvando em: ${siteDir}\n`); // Argumentos do wget - ABORDAGEM SIMPLES E EFICAZ const wgetArgs = [ '--recursive', // Baixar recursivamente '--no-clobber', // Não sobrescrever arquivos '--page-requisites', // Baixar CSS, JS, imagens '--html-extension', // Adicionar .html '--convert-links', // Converter links para locais '--restrict-file-names=windows', // Nomes compatíveis com Windows '--domains=' + urlObj.hostname, // Apenas este domínio '--no-parent', // Não subir diretórios '--no-check-certificate', // Ignorar SSL '-e', 'robots=off', // Ignorar robots.txt `-P`, siteDir, // Diretório de saída url ]; return new Promise((resolve, reject) => { console.log('🔄 Iniciando clonagem...\n'); const wgetProcess = spawn(wgetCommand, wgetArgs, { cwd: this.baseOutputDir, shell: true }); let output = ''; let errorOutput = ''; wgetProcess.stdout.on('data', (data) => { const text = data.toString(); output += text; // Mostrar progresso const lines = text.split('\n'); lines.forEach(line => { if (line.includes('%') || line.includes('saved') || line.includes('=>')) { console.log(` ${line.trim()}`); } }); }); wgetProcess.stderr.on('data', (data) => { const text = data.toString(); errorOutput += text; // Mostrar erros importantes if (text.includes('ERROR') || text.includes('failed')) { console.log(` ⚠️ ${text.trim()}`); } }); wgetProcess.on('close', (code) => { console.log(`\n${'='.repeat(60)}`); if (code === 0 || code === 8) { // 8 = alguns arquivos falharam, mas ok console.log(`✅ CLONAGEM CONCLUÍDA!`); console.log(`${'='.repeat(60)}`); console.log(`📁 Localização: ${siteDir}`); // Baixar imagens faltantes manualmente console.log(`\n🔍 Verificando imagens faltantes...`); this.downloadMissingImages(siteDir, url).then(() => { console.log(`🌐 Abra o arquivo index.html no navegador`); console.log(`${'='.repeat(60)}\n`); }); // Criar arquivo de informações const info = { originalUrl: url, clonedAt: new Date().toISOString(), directory: siteDir, method: 'wget', success: true }; fs.writeFileSync( path.join(siteDir, 'clone-info.json'), JSON.stringify(info, null, 2) ); // Criar arquivo .bat para abrir const indexPath = this.findIndexFile(siteDir); if (indexPath) { fs.writeFileSync( path.join(siteDir, 'abrir-site.bat'), `@echo off\nstart "" "${indexPath}"` ); } resolve({ url: url, success: true, directory: siteDir, method: 'wget', exitCode: code, cloneInfo: info }); } else { console.log(`❌ ERRO NA CLONAGEM (código: ${code})`); console.log(`${'='.repeat(60)}\n`); reject(new Error(`wget falhou com código ${code}\n${errorOutput}`)); } }); wgetProcess.on('error', (error) => { console.error(`❌ Erro ao executar wget: ${error.message}`); reject(error); }); }); } async downloadMissingImages(siteDir, baseUrl) { try { const indexPath = path.join(siteDir, 'index.html'); if (!fs.existsSync(indexPath)) return; const html = fs.readFileSync(indexPath, 'utf8'); // Procurar por todas as referências de imagens const imagePatterns = [ /background-image="([^"]+)"/g, /src="([^"]+\.(jpg|jpeg|png|gif|webp|svg))"/gi, /href="([^"]+\.(jpg|jpeg|png|gif|webp|svg))"/gi, ]; const imagesToDownload = new Set(); for (const pattern of imagePatterns) { let match; while ((match = pattern.exec(html)) !== null) { const imagePath = match[1]; if (imagePath.startsWith('/ws/') || imagePath.startsWith('ws/')) { imagesToDownload.add(imagePath.replace(/^\//, '')); } } } console.log(`📦 Encontradas ${imagesToDownload.size} referências de imagens`); let downloaded = 0; for (const imagePath of imagesToDownload) { const localPath = path.join(siteDir, imagePath); // Se já existe, pular if (fs.existsSync(localPath)) continue; // Criar diretório se não existir const dir = path.dirname(localPath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } // Baixar imagem try { const urlObj = new URL(baseUrl); const imageUrl = `${urlObj.protocol}//${urlObj.hostname}/${imagePath}`; console.log(` 📥 Baixando: ${imagePath}`); const response = await axios.get(imageUrl, { responseType: 'arraybuffer', timeout: 30000, headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' } }); fs.writeFileSync(localPath, response.data); downloaded++; } catch (error) { console.log(` ⚠️ Falha ao baixar: ${imagePath}`); } } console.log(`✅ ${downloaded} imagens adicionais baixadas`); } catch (error) { console.log(`⚠️ Erro ao baixar imagens faltantes: ${error.message}`); } } findIndexFile(directory) { // Procurar por index.html recursivamente const findIndex = (dir) => { const files = fs.readdirSync(dir); for (const file of files) { const fullPath = path.join(dir, file); const stat = fs.statSync(fullPath); if (stat.isDirectory()) { const found = findIndex(fullPath); if (found) return found; } else if (file === 'index.html') { return fullPath; } } return null; }; return findIndex(directory); } } // API app.post('/wget-clone', async (req, res) => { try { const { url } = req.body; if (!url) { return res.status(400).json({ error: 'URL obrigatória' }); } const cloner = new WgetCloner(); const result = await cloner.cloneWebsite(url); res.json({ success: true, data: result }); } catch (error) { console.error('❌ Erro:', error.message); res.status(500).json({ success: false, error: error.message }); } }); app.get('/health', (req, res) => { res.json({ status: 'ok', service: 'wget-cloner' }); }); app.listen(3002, () => { console.log('\n' + '='.repeat(60)); console.log('🌐 WGET Cloner - Clonagem Profissional de Sites'); console.log('='.repeat(60)); console.log('📡 Rodando em: http://localhost:3002'); console.log('✅ Clonagem de alta fidelidade com wget'); console.log('='.repeat(60) + '\n'); });