wnacg Download

Download images from wnacg gallery as a zip archive

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Advertisement:

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

Advertisement:

// ==UserScript==
// @name         wnacg Download
// @version      0.1
// @description  Download images from wnacg gallery as a zip archive
// @match        *://*.wnacg.com/*
// @match        *://*.wnacg.ru/*
// @match        *://wnacg01.link/*
// @grant        GM_xmlhttpRequest
// @connect      *
// @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';

    const container = document.querySelector('.asTBcell.uwthumb');
    if (!container) return;

    const aidMatch = location.href.match(/-aid-(\d+)/);
    if (!aidMatch) return;
    const aid = aidMatch[1];

    const downloadBtn = document.createElement('a');
    downloadBtn.id = 'download-fetch-zip';
    downloadBtn.className = 'btn';
    downloadBtn.style.width = '130px';
    downloadBtn.style.marginTop = '10px';
    downloadBtn.style.backgroundColor = '#d22';
    downloadBtn.style.borderColor = '#c11';
    downloadBtn.style.color = '#fff';
    downloadBtn.style.cursor = 'pointer';
    downloadBtn.textContent = 'Download ZIP';

    const readerBtn = document.getElementById('reader-btn');
    if (readerBtn) {
        readerBtn.parentNode.insertBefore(downloadBtn, readerBtn.nextSibling);
    } else {
        container.appendChild(downloadBtn);
    }

    const statusContainer = document.createElement('div');
    statusContainer.style.marginTop = '10px';
    statusContainer.style.fontSize = '12px';
    statusContainer.style.color = '#666';
    statusContainer.style.lineHeight = '1.4';
    container.appendChild(statusContainer);

    function delay(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }

    function transformThumbToOriginal(thumbSrc) {
        let src = thumbSrc;
        if (src.includes('/data/thumb/')) {
            src = src.replace('/data/thumb/', '/data/');
        }
        src = src.replace(/\/\/t(\d*)\./, '//img$1.');
        return src;
    }

    function downloadImageWithGM(url) {
        return new Promise((resolve, reject) => {
            if (url.startsWith('//')) {
                url = location.protocol + url;
            }
            GM_xmlhttpRequest({
                method: 'GET',
                url: url,
                responseType: 'blob',
                headers: {
                    "Referer": location.origin + '/',
                    "Accept": "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8"
                },
                onload: function(response) {
                    if (response.status === 200) {
                        resolve(response.response);
                    } else {
                        reject(new Error(`Status ${response.status}`));
                    }
                },
                onerror: function() {
                    reject(new Error('Network error'));
                }
            });
        });
    }

    async function downloadWithRetry(url, retries = 3) {
        for (let i = 0; i < retries; i++) {
            try {
                const blob = await downloadImageWithGM(url);
                return blob;
            } catch (e) {
                if (i === retries - 1) throw e;
                await delay(1500);
            }
        }
    }

    async function fetchRealUrlFromPage(photoPagePath) {
        const pageUrl = location.origin + photoPagePath;
        try {
            const response = await fetch(pageUrl, { credentials: 'same-origin' });
            const text = await response.text();
            const doc = new DOMParser().parseFromString(text, 'text/html');
            const imgElement = doc.querySelector('#picarea');
            if (imgElement && imgElement.src) {
                return imgElement.src;
            }
        } catch (e) {
            console.error('Резервный запрос к HTML не удался', e);
        }
        return null;
    }

    async function getAllItemsData() {
        const uniqueItems = [];
        const seenUrls = new Set();
        
        const maxPageMatch = document.querySelectorAll('.paginator a');
        let maxPage = 1;
        maxPageMatch.forEach(a => {
            const m = a.href.match(/page-(\d+)/);
            if (m) maxPage = Math.max(maxPage, parseInt(m[1]));
        });

        function extractFromDoc(doc) {
            doc.querySelectorAll('.gallary_item .pic_box').forEach(box => {
                const a = box.querySelector('a');
                const img = box.querySelector('img');
                if (a && img) {
                    const pageUrl = a.getAttribute('href');
                    if (!seenUrls.has(pageUrl)) {
                        seenUrls.add(pageUrl);
                        uniqueItems.push({
                            pageUrl: pageUrl,
                            guessedUrl: transformThumbToOriginal(img.src)
                        });
                    }
                }
            });
        }

        extractFromDoc(document);

        for (let p = 2; p <= maxPage; p++) {
            statusContainer.textContent = `Сканирование страницы ${p} из ${maxPage}...`;
            const indexUrl = `${location.origin}/photos-index-page-${p}-aid-${aid}.html`;
            try {
                const response = await fetch(indexUrl, { credentials: 'same-origin' });
                if (response.ok) {
                    const text = await response.text();
                    const doc = new DOMParser().parseFromString(text, 'text/html');
                    extractFromDoc(doc);
                }
            } catch (e) {
                console.error(`Ошибка при сканировании страницы ${p}:`, e);
            }
            await delay(300);
        }
        
        return uniqueItems;
    }

    async function downloadImages() {
        try {
            downloadBtn.style.pointerEvents = 'none';
            downloadBtn.style.opacity = '0.5';

            statusContainer.textContent = 'Сбор всех изображений с галереи...';
            const itemsData = await getAllItemsData();

            if (itemsData.length === 0) {
                statusContainer.textContent = 'Изображения не найдены.';
                return;
            }

            const zip = new JSZip();
            const errors = [];
            const titleElement = document.querySelector('h2');
            let title = titleElement ? titleElement.textContent.trim() : `wnacg_${aid}`;
            title = title.replace(/[\\/:*?"<>|]/g, ''); 

            let count = 1;

            for (let item of itemsData) {
                statusContainer.textContent = `Скачивание: ${count} из ${itemsData.length}...`;
                let success = false;
                let imageUrl = item.guessedUrl;
                let blob;

                try {
                    blob = await downloadWithRetry(imageUrl, 2);
                    success = true;
                } catch (e) {
                    console.warn(`Ошибка по прямой ссылке ${imageUrl}, попытка резервного запроса...`);
                    const realUrl = await fetchRealUrlFromPage(item.pageUrl);
                    if (realUrl) {
                        imageUrl = realUrl;
                        try {
                            blob = await downloadWithRetry(imageUrl, 3);
                            success = true;
                        } catch (e2) {
                            console.error(`Не удалось загрузить и по точной ссылке: ${imageUrl}`, e2);
                        }
                    }
                }

                if (success && blob) {
                    const urlPathParts = imageUrl.split('?')[0].split('/');
                    const originalFullName = urlPathParts[urlPathParts.length - 1];
                    const nameParts = originalFullName.split('.');
                    const originalExt = nameParts.length > 1 ? nameParts.pop() : '';
                    const originalName = nameParts.join('.');

                    let ext = 'jpg';
                    if (blob.type) {
                        const mime = blob.type.toLowerCase();
                        if (mime.includes('webp')) ext = 'webp';
                        else if (mime.includes('png')) ext = 'png';
                        else if (mime.includes('gif')) ext = 'gif';
                        else if (mime.includes('jpeg') || mime.includes('jpg')) ext = 'jpg';
                        else if (mime.includes('avif')) ext = 'avif';
                        else {
                            ext = originalExt.length <= 4 && originalExt.length > 0 ? originalExt : 'jpg';
                        }
                    } else {
                        ext = originalExt.length <= 4 && originalExt.length > 0 ? originalExt : 'jpg';
                    }

                    const paddedCount = String(count).padStart(3, '0');
                    let fileName;

                    if (/^\d+$/.test(originalName)) {
                        fileName = `${paddedCount}.${ext}`;
                    } else {
                        fileName = `${paddedCount}_${originalName}.${ext}`;
                    }

                    zip.file(fileName, blob);
                } else {
                    errors.push(`URL: ${imageUrl}\nОшибка: Загрузка не удалась (статус 403 или 404)`);
                }
                
                count++;
                await delay(200); 
            }

            if (errors.length > 0) {
                zip.file('errors_log.txt', errors.join('\n\n'));
            }

            statusContainer.textContent = 'Архивация ZIP...';
            const content = await zip.generateAsync({ type: 'blob' });
            saveAs(content, `${title}.zip`);
            statusContainer.textContent = `Загрузка завершена! Сохранено: ${count - 1 - errors.length} шт.`;

        } catch (err) {
            console.error('Критическая ошибка:', err);
            statusContainer.textContent = 'Произошла ошибка (подробности в консоли).';
        } finally {
            downloadBtn.style.pointerEvents = 'auto';
            downloadBtn.style.opacity = '1';
        }
    }

    downloadBtn.addEventListener('click', function(e) {
        e.preventDefault();
        downloadImages();
    });

})();