// ==UserScript==
// @name         WebURL Image Downloader with Multiple Buttons and Fixes
// @namespace    http://tampermonkey.net/
// @version      1.8
// @description  Download images from specified WebURLs as a ZIP file, log image URLs, and add DOWNLOAD buttons in multiple locations correctly.
// @author       Your Name
// @match        http://d15.876515.xyz/*
// @match        http://98.987328.xyz/*
// @match        http://97.987327.xyz/*
// @match        https://www.2xiuren.com/*
// @connect      d15.876515.xyz
// @connect      98.987328.xyz
// @connect      97.987327.xyz
// @connect      www.2xiuren.com
// ==/UserScript==

(function() {
    'use strict';

    console.log('WebURL Image Downloader script initializing...');

    async function injectScript(url) {
        return new Promise((resolve, reject) => {
            const script = document.createElement('script');
            script.src = url;
            script.onload = () => {
                console.log(`Injected script loaded: ${url}`);
                resolve();
            };
            script.onerror = () => {
                console.error(`Failed to load script: ${url}`);
                reject(new Error(`Failed to load script: ${url}`));
            };
            document.head.appendChild(script);
        });
    }

    async function fetchImage(url) {
        console.log(`Fetching image: ${url}`);
        const response = await fetch(url);
        if (!response.ok) throw new Error(`Failed to fetch image: ${url} - Status: ${response.status}`);
        return await response.blob();
    }

    async function fetchHTML(url) {
        console.log(`Fetching HTML from: ${url}`);
        const response = await fetch(url);
        if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
        const text = await response.text();
        const parser = new DOMParser();
        return parser.parseFromString(text, 'text/html');
    }

    function getFileExtension(url) {
        const match = url.match(/\.([a-zA-Z0-9]+)(?:\?.*)?$/);
        return match ? match[1].toLowerCase() : null;
    }

    function makeAbsoluteURL(url, base) {
        try {
            return new URL(url, base || window.location.origin).href;
        } catch {
            return url;
        }
    }

    function downloadUsingAnchor(blobUrl, fileName) {
        const a = document.createElement('a');
        a.href = blobUrl;
        a.download = fileName;
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
    }

    async function createAndDownloadZip(zip, zipName = 'image.zip') {
        try {
            console.log('Generating ZIP Blob...');
            const zipBlob = await zip.generateAsync({ type: 'blob', compression: 'STORE' });

            console.log('ZIP Blob generated. Size:', zipBlob.size, 'bytes');

            const blobUrl = URL.createObjectURL(zipBlob);
            downloadUsingAnchor(blobUrl, zipName);
        } catch (error) {
            console.error(`Error creating or downloading ZIP: ${error}`);
            alert(`An error occurred while creating the ZIP file: ${error.message}`);
        }
    }

    async function downloadImages(initialDoc, useCurrentPage = false) {
        let initialPageUrl = window.location.href;

        if (!useCurrentPage && initialDoc !== document) {
            initialPageUrl = initialDoc.location ? initialDoc.location.href : window.location.href;
        }

        const jianjieDiv = initialDoc.querySelector('div.item_title div.jianjie') || initialDoc.querySelector('div.jianjie');
        if (!jianjieDiv) {
            console.error('div.jianjie not found.');
            throw new Error('div.jianjie not found.');
        }
        const jianjieText = jianjieDiv.textContent.trim();
        console.log(`Extracted jianjie text: ${jianjieText}`);

        const titleMatch = jianjieText.match(/(\[XiuRen秀人网\]\s*.*?\[\d+P\])/);
        if (!titleMatch || !titleMatch[1]) {
            console.error('TITLE not found in jianjie text.');
            throw new Error('TITLE not found.');
        }
        const TITLE = titleMatch[1].trim().replace(/[\\/:*?"<>|]/g, '_');
        console.log(`Extracted TITLE for ZIP: ${TITLE}`);

        const pageDiv = initialDoc.querySelector('div.page');
        if (!pageDiv) {
            console.error('div.page not found.');
            throw new Error('div.page not found.');
        }
        const pageLinks = pageDiv.querySelectorAll('a[href]');
        const pageUrls = Array.from(pageLinks)
            .filter(link => /^\d+$/.test(link.textContent.trim()))
            .map(link => makeAbsoluteURL(link.getAttribute('href'), initialPageUrl));

        console.log(`Total related pages found: ${pageUrls.length}`);

        const CONCURRENT_LIMIT = 5;
        let index = 0;
        const images = new Set();

        async function processNext() {
            while (index < pageUrls.length) {
                const currentIndex = index++;
                const pageUrl = pageUrls[currentIndex];
                try {
                    console.log(`Processing page: ${pageUrl}`);
                    const pageDoc = await fetchHTML(pageUrl);
                    const contentDivs = pageDoc.querySelectorAll('div.content');
                    if (contentDivs.length > 0) {
                        let imagesInPage = [];
                        contentDivs.forEach(div => {
                            const imgs = div.querySelectorAll('img[src]');
                            imagesInPage.push(...imgs);
                        });
                        console.log(`Found ${imagesInPage.length} images in ${pageUrl}`);
                        imagesInPage.forEach(img => {
                            const src = img.getAttribute('src');
                            if (src) {
                                images.add(makeAbsoluteURL(src, pageUrl));
                            }
                        });
                    }
                } catch (e) {
                    console.warn(`Failed to process ${pageUrl}: ${e.message}`);
                }
            }
        }

        const workers = [];
        for (let i = 0; i < CONCURRENT_LIMIT; i++) {
            workers.push(processNext());
        }
        await Promise.all(workers);

        if (images.size === 0) {
            console.error('No images found.');
            throw new Error('No images found.');
        }

        const imageArray = Array.from(images);
        console.log(`Total images collected: ${imageArray.length}`);

        const zip = new JSZip();
        let counter = 0;
        const totalImages = imageArray.length;
        for (const imgUrl of imageArray) {
            try {
                const blob = await fetchImage(imgUrl);
                const extension = getFileExtension(imgUrl) || 'jpg';
                const validExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'tiff'];
                const finalExtension = validExtensions.includes(extension) ? extension : 'jpg';
                const filename = `${String(counter + 1).padStart(3, '0')}.${finalExtension}`;
                zip.file(filename, blob);
                counter++;
                console.log(`Added ${filename} to ZIP (${counter}/${totalImages})`);
            } catch (e) {
                console.warn(`Failed to fetch image ${imgUrl}: ${e.message}`);
            }
        }

        if (counter === 0) {
            console.error('No images were successfully downloaded.');
            throw new Error('No images were successfully downloaded.');
        }

        await createAndDownloadZip(zip, `${TITLE}.zip`);
    }

    function addDownloadButton(targetElement, useCurrentPage = false) {
        const button = document.createElement('button');
        button.innerText = 'DOWNLOAD';
        button.classList.add('download-button');
        button.style.marginLeft = '10px';
        button.style.padding = '5px 10px';
        button.style.cursor = 'pointer';
        button.style.backgroundColor = '#28a745';
        button.style.color = '#fff';
        button.style.border = 'none';
        button.style.borderRadius = '4px';
        button.style.fontSize = '14px';
        button.style.transition = 'background-color 0.3s';

        button.addEventListener('mouseenter', () => {
            button.style.backgroundColor = '#218838';
        });
        button.addEventListener('mouseleave', () => {
            button.style.backgroundColor = '#28a745';
        });

        button.addEventListener('click', async () => {
            button.disabled = true;
            const originalText = button.innerText;
            button.innerText = 'Processing...';
            console.log(`DOWNLOAD button clicked. Use current page: ${useCurrentPage}`);

            try {
                await downloadImages(document, useCurrentPage);
                button.innerText = 'DOWNLOAD';
            } catch (error) {
                console.error(error);
                alert('An error occurred during the download process.');
                button.innerText = originalText;
            }
            button.disabled = false;
        });

        targetElement.appendChild(button);
    }

    function findAndAddButtons() {
        const titleSpanElements = document.querySelectorAll('div.title h2 a span');
        titleSpanElements.forEach(span => {
            if (!span.querySelector('button.download-button')) {
                addDownloadButton(span, false);
            }
        });

        const itemInfoDivs = document.querySelectorAll('div.item_info');
        itemInfoDivs.forEach(div => {
            if (!div.querySelector('button.download-button')) {
                addDownloadButton(div, true);
            }
        });
    }

    async function initializeScript() {
        console.log('Initializing WebURL Image Downloader script...');
        try {
            await injectScript('https://cdn.jsdelivr.net/npm/jszip@3.10.0/dist/jszip.min.js');
            if (typeof JSZip === 'undefined') {
                throw new Error('JSZip failed to load after injection.');
            }
            console.log('JSZip loaded successfully. Version:', JSZip.version);
            findAndAddButtons();
        } catch (error) {
            console.error('Failed to inject JSZip:', error);
            alert('Failed to load JSZip library. Please check the console for details.');
        }
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initializeScript);
    } else {
        initializeScript();
    }

})();

This script is able to download zip files by pressing DOWNLOAD button inside div.item_info. However, DOWNLOAD button inside div.title is not functional.

// ==UserScript==
// @name         WebURL Image Downloader with Multiple Buttons and Fixes
// @namespace    http://tampermonkey.net/
// @version      1.23
// @description  Download images from specified WebURLs as a ZIP file, log image URLs, and add DOWNLOAD buttons correctly, without leaving the page. Compatible with Chrome and Edge browsers, supports multiple pages.
// @author       Your Name
// @match        http://d15.876515.xyz/*
// @match        http://98.987328.xyz/*
// @match        http://97.987327.xyz/*
// @match        https://www.2xiuren.com/*
// @connect      *
// @grant        GM_xmlhttpRequest
// @grant        GM_download
// ==/UserScript==

(function () {
    'use strict';

    console.log('WebURL Image Downloader script initializing...');

    function makeAbsoluteURL(url, base) {
        try {
            return new URL(url, base || window.location.origin).href;
        } catch {
            console.error(`Invalid URL: ${url}`);
            return url;
        }
    }

    async function injectScript(url) {
        return new Promise((resolve, reject) => {
            const script = document.createElement('script');
            script.src = url;
            script.onload = () => {
                console.log(`Injected script loaded: ${url}`);
                resolve();
            };
            script.onerror = () => {
                console.error(`Failed to load script: ${url}`);
                reject(new Error(`Failed to load script: ${url}`));
            };
            document.head.appendChild(script);
        });
    }

    function fetchHTML(url) {
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: 'GET',
                url: url,
                onload: function (response) {
                    if (response.status >= 200 && response.status < 400) {
                        const parser = new DOMParser();
                        const doc = parser.parseFromString(response.responseText, 'text/html');
                        resolve(doc);
                    } else {
                        reject(new Error(`Failed to fetch page: ${url} - Status: ${response.status}`));
                    }
                },
                onerror: function () {
                    reject(new Error(`Network error while fetching page: ${url}`));
                }
            });
        });
    }

    async function fetchImage(url) {
        try {
            console.log(`Fetching image: ${url}`);
            const response = await fetch(url);
            if (!response.ok) throw new Error(`Failed to fetch image: ${url} - Status: ${response.status}`);
            return await response.blob();
        } catch (error) {
            console.error(`Error fetching image: ${error.message}`);
            return null;
        }
    }

    function getFileExtension(url) {
        const match = url.match(/\.([a-zA-Z0-9]+)(?:\?.*)?$/);
        return match ? match[1].toLowerCase() : 'jpg';
    }

    async function createAndDownloadZip(zip, zipName = 'image.zip') {
        try {
            console.log('Generating ZIP Blob...');
            const zipBlob = await zip.generateAsync({ type: 'blob', compression: 'STORE' });
            console.log('ZIP Blob generated. Size:', zipBlob.size, 'bytes');

            const blobUrl = URL.createObjectURL(zipBlob);
            if (typeof GM_download !== 'undefined') {
                GM_download({
                    url: blobUrl,
                    name: zipName,
                    saveAs: true,
                    onload: () => URL.revokeObjectURL(blobUrl),
                    onerror: (error) => console.error('Download failed:', error)
                });
            } else {
                downloadUsingAnchor(blobUrl, zipName);
            }
        } catch (error) {
            console.error(`Error creating or downloading ZIP: ${error.message}`);
            alert(`An error occurred while creating the ZIP file: ${error.message}`);
        }
    }

    function downloadUsingAnchor(blobUrl, fileName) {
        const a = document.createElement('a');
        a.href = blobUrl;
        a.download = fileName;
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
        console.log('Download link activated and removed.');
    }

    async function downloadImagesFromUrl(linkUrl) {
        try {
            const doc = await fetchHTML(linkUrl);

            const jianjieDiv = doc.querySelector('div.jianjie');
            if (!jianjieDiv) throw new Error('div.jianjie not found on linked page.');

            const jianjieText = jianjieDiv.textContent.trim();
            console.log(`Extracted jianjie text: ${jianjieText}`);

            const titleMatch = jianjieText.match(/\[.*?\d+P\]/);
            if (!titleMatch || !titleMatch[0]) {
                console.error('TITLE not found in extracted text.');
                alert('Title pattern not matched. Please check the page structure.');
                return;
            }

            const TITLE = titleMatch[0].trim().replace(/[\\/:*?"<>|]/g, '_');
            console.log(`Extracted TITLE for ZIP: ${TITLE}`);

            const images = new Set();
            const contentDivs = doc.querySelectorAll('div.content');
            for (const content of contentDivs) {
                const imageElements = content.querySelectorAll('img[src]');
                imageElements.forEach(img => {
                    const src = img.getAttribute('src');
                    if (src) {
                        images.add(makeAbsoluteURL(src, linkUrl));
                        console.log(`Image URL collected: ${src}`);
                    }
                });
            }

            if (images.size === 0) {
                console.error('No images found.');
                alert('No images found on the linked page.');
                return;
            }

            const zip = new JSZip();
            let counter = 0;
            for (const imgUrl of images) {
                const blob = await fetchImage(imgUrl);
                if (!blob) continue;

                const extension = getFileExtension(imgUrl);
                const filename = `${String(counter + 1).padStart(3, '0')}.${extension}`;
                zip.file(filename, blob);
                counter++;
                console.log(`Added ${filename} to ZIP (${counter}/${images.size})`);
            }

            if (counter === 0) {
                console.error('No images were successfully added to the ZIP.');
                alert('No images were successfully downloaded.');
                return;
            }

            await createAndDownloadZip(zip, `${TITLE}.zip`);

        } catch (error) {
            console.error(`Error during image download process: ${error.message}`);
            alert('An error occurred during the image download process.');
        }
    }

    function addDownloadButton(spanElement, linkUrl) {
        const button = document.createElement('button');
        button.innerText = 'DOWNLOAD';
        button.classList.add('download-button');
        button.style.marginLeft = '10px';
        button.style.padding = '5px 10px';
        button.style.cursor = 'pointer';
        button.style.backgroundColor = '#28a745';
        button.style.color = '#fff';
        button.style.border = 'none';
        button.style.borderRadius = '4px';
        button.style.fontSize = '14px';
        button.style.transition = 'background-color 0.3s';

        button.addEventListener('mouseenter', () => {
            button.style.backgroundColor = '#218838';
        });
        button.addEventListener('mouseleave', () => {
            button.style.backgroundColor = '#28a745';
        });

        button.addEventListener('click', async (event) => {
            event.preventDefault();  // Prevent default behavior if applicable
            button.disabled = true;
            const originalText = button.innerText;
            button.innerText = 'Processing...';
            console.log(`DOWNLOAD button clicked, processing linked page at ${linkUrl}.`);

            await downloadImagesFromUrl(linkUrl);

            button.innerText = originalText;
            button.disabled = false;
        });

        spanElement.appendChild(button);
    }

    function findAndAddButtons() {
        const titleLinks = document.querySelectorAll('div.title h2 a');
        titleLinks.forEach(link => {
            const span = link.querySelector('span');
            if (span && !span.querySelector('button.download-button')) {
                const linkUrl = makeAbsoluteURL(link.getAttribute('href'), window.location.href);
                addDownloadButton(span, linkUrl);
            }
        });

        const itemInfoDivs = document.querySelectorAll('div.item_info');
        itemInfoDivs.forEach(div => {
            if (!div.querySelector('button.download-button')) {
                const linkElement = div.querySelector('a[href]');
                if (linkElement) {
                    const linkUrl = makeAbsoluteURL(linkElement.getAttribute('href'), window.location.href);
                    addDownloadButton(div, linkUrl);
                }
            }
        });
    }

    async function initializeScript() {
        console.log('Initializing WebURL Image Downloader script...');
        try {
            await injectScript('https://cdn.jsdelivr.net/npm/jszip@3.10.0/dist/jszip.min.js');
            if (typeof JSZip === 'undefined') {
                throw new Error('JSZip failed to load after injection.');
            }
            console.log('JSZip loaded successfully. Version:', JSZip.version);
            findAndAddButtons();
        } catch (error) {
            console.error('Failed to inject JSZip:', error.message);
            alert('Failed to load JSZip library. Please check the console for details.');
        }
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initializeScript);
    } else {
        initializeScript();
    }

})();

As for this script, by using div.title button,it is able to copy 3 images but not able to download in Violentmonkey because it doesn't support selecting location (popup window) for the zip file.  However DOWNLOAD button in div.item_info doesn't work.
Here are my requests.  I would like both buttons to perform the same action meaning collecting all images, create a zip file and download it to the browser set download location.