const fs = require('fs'); // ๐Ÿ‘ˆ Usa il modulo promises const axios = require('axios'); const path = require('path'); /** * Scarica un'immagine da una URL e la salva in una directory locale * @param {string} url - L'URL dell'immagine da scaricare * @param {string} filepath - Il percorso dove salvare l'immagine scaricata */ class ImageDownloader { /** * Scarica un'immagine da una URL e la salva in una directory locale. * Tenta di scaricare fino a 3 volte in caso di errore, con un ritardo tra i tentativi. * * @param {string} url - L'URL dell'immagine da scaricare * @param {string} filepath - Il percorso dove salvare l'immagine scaricata * @param {number} maxRetries - Numero massimo di tentativi in caso di fallimento (default: 3) * @param {number} delay - Ritardo in millisecondi tra i tentativi (default: 1000) * @returns {Promise} */ async downloadImage(url, filepath, options = {}) { const { maxRetries = 3, // Aumentato il numero di tentativi predefiniti initialDelay = 1000, // Ritardo iniziale maxDelay = 10000, // Ritardo massimo timeout = 30000, // Timeout della richiesta validateContentType = true, // Verifica del tipo di contenuto nomefileoriginale = true, } = options; // Funzione per il backoff esponenziale const getDelay = (attempt) => { return Math.min(initialDelay * Math.pow(2, attempt - 1), maxDelay); }; for (let attempt = 1; attempt <= maxRetries; attempt++) { try { // Verifica se il filepath esiste giร  if (await this.isFileExistsAsync(filepath)) { fs.unlinkSync(filepath); } if (attempt > 1) console.log(`๐Ÿ“ฅ Tentativo ${attempt}/${maxRetries} - Scaricamento: ${url}`); const response = await axios({ url, method: 'GET', responseType: 'stream', timeout: timeout, maxRedirects: 5, headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', Accept: 'image/jpeg,image/png,image/webp,image/gif,image/*', // Specifico per immagini 'Cache-Control': 'no-cache', // Evita problemi di caching Connection: 'keep-alive', }, validateStatus: (status) => status === 200, // Per immagini ci aspettiamo 200 maxContentLength: 10 * 1024 * 1024, // Limite di 10MB per immagine }); // Verifica del content-type se richiesto if (validateContentType) { const contentType = response.headers['content-type']; if (!contentType || !contentType.startsWith('image/')) { throw new Error(`Content-Type non valido: ${contentType}`); } } // Verifica della dimensione del file const contentLength = parseInt(response.headers['content-length']); if (contentLength && contentLength > 100 * 1024 * 1024) { // 100MB limit throw new Error('File troppo grande'); } let downloadedBytes = 0; response.data.on('data', (chunk) => { downloadedBytes += chunk.length; }); let writer = null; if (nomefileoriginale) { // Estrai il nome del file dall'URL o da Content-Disposition //let fileName = this.extractFileNameFromUrl(url) || this.extractFileNameFromHeaders(response.headers); let fileName = path.basename(response.data.responseUrl); // Se il nome del file non รจ specificato, genera un nome predefinito if (!fileName) { fileName = `image_${Date.now()}.jpg`; } // Genera il percorso completo del file const fullPath = path.join(path.dirname(filepath), fileName); filepath = fullPath; } // Scrivi il file sul disco writer = fs.createWriteStream(filepath); response.data.pipe(writer); await new Promise((resolve, reject) => { writer.on('finish', resolve); writer.on('error', (error) => { fs.unlink(filepath, () => {}); // Pulizia in caso di errore reject(error); }); response.data.on('error', (error) => { fs.unlink(filepath, () => {}); reject(error); }); }); console.info(`โœ… Immagine scaricata con successo in ${filepath}`); return { ris: true, filepath }; } catch (error) { console.error(`โŒ Errore nel tentativo ${attempt}/${maxRetries}:`, error.message); // Pulizia del file in caso di errore if (await this.isFileExistsAsync(filepath)) { fs.unlinkSync(filepath); } // se in error.message c'รจ '404' allora esci e ritorna code: 404 if (error.message.includes('404')) { return { ris: false, code: 404 }; } if (attempt === maxRetries) { console.error(`Download fallito dopo ${maxRetries} tentativi: ${error.message}`); return { ris: false }; } const delay = getDelay(attempt); console.info(`๐Ÿ”„ Attendo ${delay}ms prima del prossimo tentativo...`); await new Promise((resolve) => setTimeout(resolve, delay)); } } } // Funzione per estrarre il nome del file dall'URL extractFileNameFromUrl(url) { const match = url.match(/\/([^/?#]+)(?:[?#]|$)/); return match ? decodeURIComponent(match[1]) : null; } // Funzione per estrarre il nome del file da Content-Disposition extractFileNameFromHeaders(headers) { const contentDisposition = headers['content-disposition']; if (contentDisposition) { const match = contentDisposition.match(/filename="([^"]+)"/); if (match) { return decodeURIComponent(match[1]); } } return null; } async isFileExistsAsync (filename) { try { let fileExists = await fs.promises .stat(filename) .then(() => true) .catch(() => false); // console.log(filename, 'esiste', fileExists) return fileExists; } catch (e) { // console.log(filename, 'esiste', 'FALSE') return false; } } } module.exports = ImageDownloader;