wnacg Download

Download images from wnacg gallery as a zip archive

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği yüklemek için Tampermonkey gibi bir uzantı yüklemeniz gerekir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği indirebilmeniz için ayrıca Tampermonkey gibi bir eklenti kurmanız gerekmektedir.

Bu komut dosyasını yüklemek için bir kullanıcı komut dosyası yöneticisi uzantısı yüklemeniz gerekecek.

(Zaten bir kullanıcı komut dosyası yöneticim var, kurmama izin verin!)

Advertisement:

Bu stili yüklemek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için Stylus gibi bir uzantı kurmanız gerekir.

Bu stili yükleyebilmek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı kurmanız gerekir.

Bu stili yükleyebilmek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

(Zateb bir user-style yöneticim var, yükleyeyim!)

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

})();