nhentai Download

Download images from nhentai gallery as a zip archive

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

// ==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.';
        });
    });
})();