您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Download images from nhentai gallery as a zip archive
// ==UserScript== // @name nhentai Download // @version 1.5 // @description Download images from nhentai gallery as a zip archive // @match https://nhentai.net/g/* // @grant GM_xmlhttpRequest // @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.5.0/jszip.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js // @namespace https://greasyfork.org/users/789838 // ==/UserScript== (function() { 'use strict'; // Создание кнопки "Download Fetch" const buttonsContainer = document.querySelector('.buttons'); const fetchButton = document.createElement('button'); fetchButton.id = 'download-fetch'; fetchButton.className = 'btn btn-secondary'; fetchButton.innerHTML = '<i class="fa fa-download"></i> Download Fetch'; buttonsContainer.appendChild(fetchButton); // Создаем элемент для отображения статуса загрузки const statusContainer = document.createElement('div'); statusContainer.style.marginTop = '10px'; statusContainer.style.fontSize = '14px'; statusContainer.style.color = '#666'; buttonsContainer.appendChild(statusContainer); // Функция для задержки между запросами function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } // Функция для получения ссылок на изображения с подстраниц с обработкой ошибок async function getImageUrls() { const imageUrls = []; const pageLinks = document.querySelectorAll('.gallerythumb'); const retryDelay = 2000; // Задержка перед повтором запроса в случае ошибки (5 секунд) const delayBetweenRequests = 200; // Задержка между запросами (1 секунда) statusContainer.textContent = `Found ${pageLinks.length} pages. Fetching images...`; for (let i = 0; i < pageLinks.length; i++) { const link = pageLinks[i]; const pageUrl = link.href; statusContainer.textContent = `Processing page ${i + 1} of ${pageLinks.length}`; let success = false; let attempts = 0; while (!success && attempts < 3) { // Пытаемся получить изображение, максимум 3 попытки attempts++; try { const response = await fetch(pageUrl); if (response.status === 429) { statusContainer.textContent = `Too Many Requests. Waiting for ${retryDelay / 1000} seconds...`; await delay(retryDelay); continue; } const text = await response.text(); const parser = new DOMParser(); const doc = parser.parseFromString(text, 'text/html'); const imgElement = doc.querySelector('#image-container img'); if (imgElement) { const imageUrl = imgElement.src; imageUrls.push(imageUrl); success = true; } else { console.error(`Image not found on page: ${pageUrl}`); success = true; // Прекращаем попытки, так как элемент не найден } } catch (error) { console.error(`Failed to fetch page: ${pageUrl}. Attempt ${attempts} of 3`, error); if (attempts >= 3) { statusContainer.textContent = `Failed to fetch after 3 attempts. Skipping page ${i + 1}`; } else { statusContainer.textContent = `Error fetching page ${i + 1}. Retrying in ${retryDelay / 1000} seconds...`; await delay(retryDelay); } } } // Задержка перед следующим запросом if (success) { await delay(delayBetweenRequests); } } statusContainer.textContent = `Image URLs fetched successfully!`; return imageUrls; } // Функция для загрузки изображения с использованием GM_xmlhttpRequest function downloadImageWithGM(url) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: url, responseType: 'blob', onload: function(response) { if (response.status === 200) { resolve(response.response); } else { reject(new Error(`Failed to download image: ${url}`)); } }, onerror: function() { reject(new Error(`Failed to download image: ${url}`)); } }); }); } // Функция для скачивания изображений и создания архива async function downloadImages() { const imageUrls = await getImageUrls(); if (imageUrls.length === 0) { statusContainer.textContent = 'No images found. Exiting download.'; return; } const zip = new JSZip(); const errors = []; const titleElement = document.querySelector('h1.title .pretty'); let title = titleElement.textContent.split('|')[0].trim(); let count = 1; statusContainer.textContent = 'Starting download...'; for (let imageUrl of imageUrls) { try { const blob = await downloadImageWithGM(imageUrl); const fileName = `${count} - ${imageUrl.split('/').pop()}`; zip.file(fileName, blob); statusContainer.textContent = `Downloaded ${count} of ${imageUrls.length} images`; count++; } catch (error) { console.error(`Failed to download image: ${imageUrl}`, error); errors.push(`Image URL: ${imageUrl}\nError: ${error.message}`); } } // Добавление логов ошибок в архив errors.forEach((error, index) => { zip.file(`log ${index + 1}.txt`, error); }); zip.generateAsync({ type: 'blob' }).then(function(content) { saveAs(content, `${title}.zip`); statusContainer.textContent = 'Download completed!'; }).catch(error => { console.error('Failed to generate zip:', error); statusContainer.textContent = 'Failed to generate zip. Check console for details.'; }); } // Обработчик нажатия на кнопку fetchButton.addEventListener('click', function() { statusContainer.textContent = 'Starting the process...'; downloadImages().catch(error => { console.error('Error during download:', error); statusContainer.textContent = 'Error during download. Check console for details.'; }); }); })();