wnacg Download

Download images from wnacg gallery as a zip archive

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

Advertisement:

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

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();
    });

})();