359 lines
12 KiB
JavaScript
359 lines
12 KiB
JavaScript
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, customFolderName = null) {
|
|
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 finalFolderName = customFolderName ? this.sanitizeFilename(customFolderName) : `${siteName}_${timestamp}`;
|
|
const siteDir = path.join(this.baseOutputDir, finalFolderName);
|
|
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
|
|
'--span-hosts', // Permitir redirecionamentos entre subdomínios/CDN
|
|
'--user-agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"',
|
|
'--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}`);
|
|
|
|
const { execSync } = require('child_process');
|
|
// Mover arquivos do subdiretório do domínio para a raiz do siteDir
|
|
try {
|
|
const domainDir = path.join(siteDir, urlObj.hostname);
|
|
if (fs.existsSync(domainDir)) {
|
|
execSync(`cp -r "${domainDir}"/* "${siteDir}"/ 2>/dev/null && rm -rf "${domainDir}"`);
|
|
}
|
|
} catch (e) {}
|
|
|
|
// 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`);
|
|
});
|
|
|
|
// countFiles inline helper
|
|
var countFiles = function(dir) { var ts=0,fc=0; (function rec(d){if(!fs.existsSync(d))return;fs.readdirSync(d).forEach(function(i){var p=path.join(d,i),s=fs.statSync(p);if(s.isDirectory()&&i.indexOf("cloned-sites")==-1&&i.indexOf(".versions")==-1)rec(p);else if(!s.isDirectory()){ts+=s.size;fc++;}});})(dir);return{fileCount:fc,totalSize:ts};};
|
|
|
|
// Criar arquivo de informações
|
|
const stats = countFiles(siteDir);
|
|
const info = {
|
|
originalUrl: url,
|
|
clonedAt: new Date().toISOString(),
|
|
directory: siteDir,
|
|
method: 'wget',
|
|
success: true,
|
|
fileCount: stats.fileCount,
|
|
totalSize: stats.totalSize
|
|
};
|
|
|
|
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,
|
|
files: stats.fileCount,
|
|
size: stats.totalSize,
|
|
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, folderName } = req.body;
|
|
if (!url) {
|
|
return res.status(400).json({ error: 'URL obrigatória' });
|
|
}
|
|
|
|
const cloner = new WgetCloner();
|
|
const result = await cloner.cloneWebsite(url, folderName);
|
|
|
|
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');
|
|
});
|