nhentai Download

Download images from nhentai gallery as a zip archive

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey, το Greasemonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Userscripts για να εγκαταστήσετε αυτόν τον κώδικα.

You will need to install an extension such as Tampermonkey to install this script.

Θα χρειαστεί να εγκαταστήσετε μια επέκταση διαχείρισης κώδικα χρήστη για να εγκαταστήσετε αυτόν τον κώδικα.

(Έχω ήδη έναν διαχειριστή κώδικα χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Έχω ήδη έναν διαχειριστή στυλ χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

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