// ==UserScript==
// @name         WebURL Image Downloader with Multiple Buttons and Fixes
// @namespace    http://tampermonkey.net/
// @version      1.7
// @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/*
// @grant        GM_download
// @grant        none
// @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...');

    /**
     * Injects a script tag into the page to load an external JavaScript library.
     * @param {string} url - The URL of the script to inject.
     * @returns {Promise} - Resolves when the script is loaded, rejects on error.
     */
    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);
        });
    }

    /**
     * Fetches an image as a Blob using the standard fetch API.
     * @param {string} url - The URL of the image to fetch.
     * @returns {Promise<Blob>} - The fetched image as a Blob.
     */
    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.warn(`Error fetching image ${url}: ${error}`);
            throw error;
        }
    }

    /**
     * Utility function to fetch and parse HTML from a given URL.
     * @param {string} url - The URL to fetch.
     * @returns {Promise<Document>} - The parsed HTML Document.
     */
    async function fetchHTML(url) {
        try {
            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();
            const doc = parser.parseFromString(text, 'text/html');
            return doc;
        } catch (error) {
            console.error(`Error fetching HTML from ${url}: ${error}`);
            throw error;
        }
    }

    /**
     * Helper function to get file extension from a URL.
     * @param {string} url - The URL to extract the extension from.
     * @returns {string|null} - The file extension (e.g., 'jpg', 'png') or null if none found.
     */
    function getFileExtension(url) {
        const match = url.match(/\.([a-zA-Z0-9]+)(?:\?.*)?$/);
        return match ? match[1].toLowerCase() : null;
    }

    /**
     * Helper function to make absolute URLs from relative ones.
     * @param {string} url - The relative or absolute URL.
     * @param {string} [base] - The base URL for relative URLs.
     * @returns {string} - The absolute URL.
     */
    function makeAbsoluteURL(url, base) {
        try {
            return new URL(url, base || window.location.origin).href;
        } catch {
            return url; // Return original if invalid
        }
    }

    /**
     * Downloads the image Blob directly for verification.
     * @param {Blob} imageBlob - The image as a Blob.
     * @param {string} [imageName='downloaded_image.jpg'] - The name for the downloaded image.
     */
    async function downloadImageBlobDirectly(imageBlob, imageName = 'downloaded_image.jpg') {
        try {
            const blobUrl = URL.createObjectURL(imageBlob);
            const a = document.createElement('a');
            a.href = blobUrl;
            a.download = imageName;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            URL.revokeObjectURL(blobUrl);
            console.log('Image Blob downloaded directly for verification.');
            alert('Image Blob downloaded directly for verification.');
        } catch (error) {
            console.error('Error downloading image Blob directly:', error);
            alert('Failed to download image Blob directly.');
        }
    }

    /**
     * Creates a ZIP file with the provided image Blob and triggers a download.
     * @param {JSZip} zip - The JSZip instance.
     * @param {Blob} imageBlob - The image as a Blob.
     * @param {string} imageName - The name of the image file inside the ZIP.
     * @param {string} [zipName='image.zip'] - The name of the ZIP file to download.
     */
    async function createAndDownloadZip(zip, imageBlob, imageName, zipName = 'image.zip') {
        try {
            console.log(`Adding ${imageName} to ZIP.`);
            zip.file(imageName, imageBlob);

            // Log all files in the ZIP for verification
            console.log('Current ZIP contents:', Object.keys(zip.files));

            console.log('Generating ZIP Blob...');

            // Generate the ZIP Blob without a timeout
            const zipBlob = await zip.generateAsync(
                { type: 'blob', compression: 'STORE' }, // 'STORE' for no compression
                (metadata) => {
                    console.log(`ZIP Generation Progress: ${metadata.percent.toFixed(2)}%`);
                }
            );

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

            const blobUrl = URL.createObjectURL(zipBlob);
            console.log('Blob URL created:', blobUrl);

            // Trigger the download using GM_download
            GM_download({
                url: blobUrl,
                name: zipName,
                saveAs: true,
                onload: () => {
                    console.log('Download completed.');
                    URL.revokeObjectURL(blobUrl);
                },
                onerror: (error) => {
                    console.error('Download failed:', error);
                    alert('Failed to download ZIP file.');
                }
            });

            alert('Download initiated successfully.');
        } catch (error) {
            console.error(`Error creating or downloading ZIP: ${error}`);
            alert(`An error occurred while creating the ZIP file: ${error.message}`);
        }
    }

    /**
     * Downloads the first image found on the page as a ZIP file.
     * @param {Document} initialDoc - The initial Document object (current page or fetched page).
     * @param {boolean} useCurrentPage - Whether to use the current page's URL for fetching images.
     */
    async function downloadImages(initialDoc, useCurrentPage = false) {
        let initialPageUrl = window.location.href;

        if (!useCurrentPage && initialDoc !== document) {
            // If a different page's DOM is provided, determine its URL
            initialPageUrl = initialDoc.location ? initialDoc.location.href : window.location.href;
        }

        // Extract TITLE from div.jianjie
        const jianjieDiv = initialDoc.querySelector('div.item_title div.jianjie') || initialDoc.querySelector('div.jianjie');
        if (!jianjieDiv) {
            console.error('div.jianjie not found.');
            if (!useCurrentPage) {
                console.log('Fetched HTML body:', initialDoc.body.innerHTML);
            }
            throw new Error('div.jianjie not found.');
        }
        const jianjieText = jianjieDiv.textContent.trim();
        console.log(`Extracted jianjie text: ${jianjieText}`);

        // Corrected Regex to capture the entire title including [73P]
        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, '_'); // Replace illegal filename characters
        console.log(`Extracted TITLE for ZIP: ${TITLE}`);

        // Extract all related PageURLs from div.page
        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 => {
                                  let href = link.getAttribute('href');
                                  // Ensure the href is absolute
                                  if (!href.startsWith('http')) {
                                      href = makeAbsoluteURL(href, initialPageUrl);
                                  }
                                  return href;
                              });

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

        if (pageUrls.length === 0) {
            console.error('No page URLs found.');
            throw new Error('No page URLs found.');
        }

        // Limit concurrent fetches to avoid overloading
        const CONCURRENT_LIMIT = 5;
        let index = 0;

        // Collect all image URLs
        const images = new Set(); // Use Set to avoid duplicates

        // Define processNext inside downloadImages to access 'index'
        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}`);
                        if (imagesInPage.length === 0) {
                            console.warn(`No images found in any div.content of ${pageUrl}. Inspecting div.content HTML:`);
                            contentDivs.forEach(div => {
                                console.log(div.innerHTML);
                            });
                        }
                        imagesInPage.forEach(img => {
                            const src = img.getAttribute('src');
                            if (src) {
                                images.add(src);
                                // Log each image URL as it's collected
                                console.log(`Collected Image URL: ${makeAbsoluteURL(src, pageUrl)}`);
                            }
                        });
                    } else {
                        console.warn(`No div.content found in ${pageUrl}`);
                    }
                } catch (e) {
                    console.warn(`Failed to process ${pageUrl}: ${e.message}`);
                }
            }
        }

        // Start concurrent workers
        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.');
        }

        // Convert Set to Array for processing
        const imageArray = Array.from(images);
        console.log(`Total images collected: ${imageArray.length}`);

        // Log all collected image URLs to the console
        imageArray.forEach((imgUrl, idx) => {
            console.log(`Image ${idx + 1}: ${makeAbsoluteURL(imgUrl, initialPageUrl)}`);
        });

        // Initialize JSZip
        const zip = new JSZip();

        // Fetch all images and add to zip
        let counter = 0;
        const totalImages = imageArray.length;
        for (const imgUrl of imageArray) {
            try {
                const absoluteImgUrl = makeAbsoluteURL(imgUrl, initialPageUrl);
                const blob = await fetchImage(absoluteImgUrl);
                const extension = getFileExtension(imgUrl) || 'jpg';
                // Ensure the extension is valid
                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++;
                // Log progress
                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.');
        }

        // Create and download the ZIP using the injected JSZip
        await createAndDownloadZip(zip, null, null, `${TITLE}.zip`);
    }

    /**
     * Creates and injects a styled DOWNLOAD button into a target element.
     * @param {HTMLElement} targetElement - The element to which the button will be appended.
     * @param {boolean} useCurrentPage - Whether to use the current page's URL for fetching images.
     */
    function addDownloadButton(targetElement, useCurrentPage = false) {
        // Create a styled button
        const button = document.createElement('button');
        button.innerText = 'DOWNLOAD';
        button.classList.add('download-button'); // Unique class to prevent duplication
        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';

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

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

            try {
                // Initialize JSZip
                const zip = new JSZip();

                // Determine the page URL to fetch
                const pageUrl = useCurrentPage ? window.location.href : undefined;

                // Fetch and process images
                await downloadImages(document, useCurrentPage);

                // Re-enable the button after processing
                button.innerText = 'DOWNLOAD';
                button.disabled = false;
            } catch (error) {
                console.error(error);
                alert('An error occurred during the download process.');
                button.innerText = originalText;
                button.disabled = false;
            }
        });

        // Append the button to the target element
        targetElement.appendChild(button);
    }

    /**
     * Finds all target elements in the page and adds DOWNLOAD buttons to them.
     */
    function findAndAddButtons() {
        // Existing buttons in div.title > h2 > a > span
        const titleSpanElements = document.querySelectorAll('div.title h2 a span');
        titleSpanElements.forEach(span => {
            // Avoid adding multiple buttons
            if (!span.querySelector('button.download-button')) {
                const aTag = span.closest('a');
                if (aTag && aTag.href) {
                    // Construct absolute PageURL
                    const pageUrl = aTag.href.startsWith('http') ? aTag.href : makeAbsoluteURL(aTag.getAttribute('href'), window.location.origin);
                    console.log(`Adding DOWNLOAD button to title span with pageUrl: ${pageUrl}`);
                    addDownloadButton(span, false); // useCurrentPage=false
                }
            }
        });

        // New buttons in div.item_info
        const itemInfoDivs = document.querySelectorAll('div.item_info');
        itemInfoDivs.forEach(div => {
            // Avoid adding multiple buttons
            if (!div.querySelector('button.download-button')) {
                console.log(`Adding DOWNLOAD button to item_info div using current page.`);
                addDownloadButton(div, true); // useCurrentPage=true
            }
        });
    }

    /**
     * Initializes the script by injecting JSZip and setting up DOWNLOAD buttons.
     */
    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.');
        }
    }

    // Initialize the script after DOM is fully loaded
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initializeScript);
    } else {
        initializeScript();
    }

})();

i have problem downloading the zip file.  it gives an error: creating or downloading ZIP:  ReferenceError: GM_download is not defined
Please help me solve this problem.