Fix Mangapark Image Loading Issue

Auto-loads images from the main host to bypass CDN issues. Click "PICK" to manually change servers.

当前为 2025-12-31 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Fix Mangapark Image Loading Issue
// @namespace    http://tampermonkey.net/
// @version      0.24
// @description  Auto-loads images from the main host to bypass CDN issues. Click "PICK" to manually change servers.
// @match        https://mangapark.io/title/*-chapter-*
// @match        https://mangapark.io/title/*-ch-*
// @match        https://mangapark.io/title/*-prologue
// @match        https://mangapark.io/title/*-oneshot
// @match        https://mangapark.net/title/*-chapter-*
// @match        https://mangapark.net/title/*-ch-*
// @match        https://mangapark.net/title/*-prologue
// @match        https://mangapark.net/title/*-oneshot
// @match        https://mpark.to/title/*-chapter-*
// @match        https://mpark.to/title/*-ch-*
// @match        https://mpark.to/title/*-prologue
// @match        https://mpark.to/title/*-oneshot
// @license      MIT
// @grant        none
// @run-at       document-idle
// ==/UserScript==

(function() {
    'use strict';

    // Prevent script from running multiple times
    if (window.__mpFixInitialized) return;
    window.__mpFixInitialized = true;

    // Configuration
    const CONFIG = {
        CDN_SERVERS: [
            "s01", "s02", "s03", "s04",
            "s05", "s06", "s07", "s08", "s09", "s10"
        ],
        BUTTON_TEXT: "PICK",
        BUTTON_POSITION: {
            top: "20px",
            right: "70px"
        }
    };

    // Regex to detect the problematic CDN subdomains (s00-s10)
    const SUBDOMAIN_REGEX = /(https?:)?\/\/s(\d{2})\./i;

    let pickerMode = false;
    let currentHover = null;

    /**
     * Generates a "Fixed" URL using the current page's host.
     * This mimics the logic of the working script you provided.
     */
    function getMainHostUrl(originalSrc) {
        try {
            const url = new URL(originalSrc);
            // Replace the CDN host with the current page's host
            return `https://${window.location.host}${url.pathname}`;
        } catch (e) {
            return originalSrc;
        }
    }

    /**
     * Check if a URL is a candidate for fixing (s00-s10 pattern)
     */
    function isSupportedImage(url) {
        return SUBDOMAIN_REGEX.test(url);
    }

    /**
     * Get next CDN server (fallback logic)
     */
    function getNextCDN(currentSrc, retryCount) {
        const match = currentSrc.match(SUBDOMAIN_REGEX);
        if (!match) return currentSrc;

        const currentServerNum = match[2]; // e.g., "04"
        let currentIndex = parseInt(currentServerNum, 10) - 1;
        if (currentIndex < 0 || currentIndex > 9) currentIndex = 0;

        const nextIndex = (currentIndex + 1 + retryCount) % CONFIG.CDN_SERVERS.length;
        return CONFIG.CDN_SERVERS[nextIndex]; // Returns "s05"
    }

    /**
     * Handle image loading errors.
     * Strategy 1: Try Main Host (if not already tried).
     * Strategy 2: Fallback to cycling CDNs.
     */
    function handleImageError(img) {
        const retryCount = Number(img.dataset.retryCount || "0");
        const originalSrc = img.src;

        if (!isSupportedImage(originalSrc) && !img.dataset.isMainHost) {
            // If we can't handle it, give up
            return;
        }

        // Step 1: If we haven't tried the Main Host fix yet, DO IT.
        if (!img.dataset.triedMainHost) {
            const newSrc = getMainHostUrl(originalSrc);
            if (newSrc !== originalSrc) {
                console.warn(`[MainHost Fix] Replacing: ${originalSrc.substring(0, 50)}...`);
                img.dataset.triedMainHost = "true";
                img.dataset.isMainHost = "true";
                img.src = '';
                setTimeout(() => { img.src = newSrc; }, 100);
                return;
            }
        }

        // Step 2: If Main Host failed or wasn't applicable, cycle CDNs
        if (retryCount < 9) {
            const nextServerPrefix = getNextCDN(originalSrc, retryCount);
            const newSrc = originalSrc.replace(SUBDOMAIN_REGEX, (match, protocol, oldNum) => {
                return (protocol || 'https:') + '//' + nextServerPrefix + '.';
            });

            console.warn(`[CDN Cycle] Attempt ${retryCount + 1}: ${nextServerPrefix}`);
            img.dataset.retryCount = String(retryCount + 1);
            img.src = '';
            setTimeout(() => { img.src = newSrc; }, 100);
        } else {
            console.error(`✗ All retries exhausted for: ${originalSrc}`);
            img.dataset.failed = "true";
            img.style.border = "2px solid red";
            img.title = "Failed to load from all sources";
        }
    }

    /**
     * Set up error handling
     */
    function setupImageErrorHandler(img) {
        if (img.dataset.mpHandlerAttached) return;
        img.dataset.mpHandlerAttached = "true";

        img.addEventListener('error', () => handleImageError(img));
        img.addEventListener('load', () => {
            delete img.dataset.failed;
            if (!img.dataset.isMainHost) {
                // If it loaded successfully from a CDN, we keep it.
                // If it loaded from Main Host, we keep it.
            }
        });
    }

    /**
     * Initializes a single image element.
     * Now performs proactive fixing based on the working script logic.
     */
    function initializeImage(img) {
        // Only process images matching the bad CDN pattern
        if (!isSupportedImage(img.src)) return;

        if (img.dataset.mpFixed) return;

        console.log(`[AutoFix] Found target: ${img.src.substring(0, 50)}...`);

        // --- PROACTIVE FIX LOGIC (From the working script) ---
        // Immediately replace the CDN URL with the Main Host URL.
        const newSrc = getMainHostUrl(img.src);

        if (img.src !== newSrc) {
            console.log(`[AutoFix] Switching to Main Host: ${newSrc.substring(0, 50)}...`);
            img.src = newSrc;
            img.dataset.isMainHost = "true"; // Mark it so we know it's the "fixed" version
        }
        // ------------------------------------------------------

        setupImageErrorHandler(img);
        img.dataset.mpFixed = "true";
    }

    /**
     * Find and initialize all images already present.
     */
    function initializeExistingImages() {
        const allImages = document.querySelectorAll('img[src]');
        allImages.forEach(img => {
            if (isSupportedImage(img.src)) {
                initializeImage(img);
            }
        });
    }

    /**
     * Observe the DOM for lazy-loaded images
     */
    function observeForChanges() {
        const observer = new MutationObserver(mutations => {
            for (const mutation of mutations) {
                if (mutation.addedNodes.length) {
                    for (const node of mutation.addedNodes) {
                        if (node.nodeType === Node.ELEMENT_NODE) {
                            if (node.tagName === 'IMG' && isSupportedImage(node.src)) {
                                initializeImage(node);
                            } else if (node.querySelectorAll) {
                                const images = node.querySelectorAll('img');
                                images.forEach(img => {
                                    if (isSupportedImage(img.src)) initializeImage(img);
                                });
                            }
                        }
                    }
                }
            }
        });

        if (document.body) {
            observer.observe(document.body, {
                childList: true,
                subtree: true
            });
        }
    }

    // --- Picker Mode Functions ---

    function enablePickerMode() {
        pickerMode = true;
        document.body.style.cursor = "crosshair";
    }

    function disablePickerMode() {
        pickerMode = false;
        document.body.style.cursor = "default";
        if (currentHover) {
            currentHover.style.outline = "";
            currentHover = null;
        }
    }

    function togglePicker() {
        pickerMode ? disablePickerMode() : enablePickerMode();
    }

    function highlight(img) {
        if (currentHover && currentHover !== img) currentHover.style.outline = "";
        currentHover = img;
        img.style.outline = "3px solid #ff5500";
    }

    function unhighlight(img) {
        img.style.outline = "";
        if (currentHover === img) currentHover = null;
    }

    function changeImagePrefix(img) {
        const original = img.src;

        // Check if it matches our regex
        const match = original.match(SUBDOMAIN_REGEX);
        const isMainHost = img.dataset.isMainHost === "true";

        let currentVal = "";
        if (isMainHost) {
            currentVal = "Main Host (Auto-Fixed)";
        } else if (match) {
            currentVal = match[0]; // e.g. "//s04."
        } else {
            alert("URL does not match a fixable pattern.");
            return;
        }

        // Prompt user for input
        // We allow: "s01".."s10", or empty to reset to Main Host
        const input = prompt(`Current: ${currentVal}\n\nEnter new server (e.g. s05) or leave empty for Main Host fix:`);

        if (input === null) return; // Cancelled

        let newSrc = "";

        if (input.trim() === "") {
            // User wants Main Host Fix
            newSrc = getMainHostUrl(original);
            img.dataset.isMainHost = "true";
        } else {
            // User wants specific Server
            const serverName = input.trim().toLowerCase();
            // Basic validation
            if (!/^s\d{2}$/.test(serverName)) {
                alert("Invalid format. Use s01, s05, etc.");
                return;
            }

            // If currently on Main Host, we don't have the original CDN URL structure handy easily
            // unless we saved it. For now, if on Main Host, we can't easily switch back to a specific
            // CDN server without knowing the original CDN domain (mpypl, mpqsc, etc).
            // However, usually the pathname is enough if we assume a domain, but the domains vary.

            if (isMainHost) {
                alert("Cannot switch to specific server from Main Host fix easily (original CDN domain unknown). Refresh the page to try CDNs.");
                return;
            }

            const protocol = match[1] || 'https:';
            newSrc = original.replace(SUBDOMAIN_REGEX, `${protocol}//${serverName}.`);
            img.dataset.isMainHost = "";
        }

        img.src = newSrc;
        img.dataset.retryCount = "0"; // Reset retry counter
        console.log("Manual update:", original, "->", img.src);
        disablePickerMode();
    }

    function bindPickerEvents() {
        document.addEventListener("mouseover", e => {
            if (!pickerMode || e.target.tagName !== "IMG") return;
            // Highlight any image, even if not strictly supported, to allow inspection
            highlight(e.target);
        });

        document.addEventListener("mouseout", e => {
            if (!pickerMode || e.target.tagName !== "IMG") return;
            unhighlight(e.target);
        });

        document.addEventListener("click", e => {
            if (!pickerMode || e.target.tagName !== "IMG") return;
            e.preventDefault();
            e.stopPropagation();
            changeImagePrefix(e.target);
            disablePickerMode();
        }, true);
    }

    function createPickerButton() {
        if (!document.body) return;
        if (document.getElementById('mp-fix-btn')) return;

        const btn = document.createElement("button");
        btn.id = 'mp-fix-btn';
        btn.textContent = CONFIG.BUTTON_TEXT;
        btn.style.cssText = `
            position: fixed;
            top: ${CONFIG.BUTTON_POSITION.top};
            right: ${CONFIG.BUTTON_POSITION.right};
            z-index: 9999;
            padding: 8px 12px;
            background: #66ccff;
            border: 1px solid #333;
            cursor: pointer;
            font-weight: bold;
        `;
        btn.onclick = togglePicker;
        document.body.appendChild(btn);
    }

    // Main initialization flow
    function main() {
        if (!document.body) {
            setTimeout(main, 100);
            return;
        }

        initializeExistingImages();
        observeForChanges();
        bindPickerEvents();
        createPickerButton();
    }

    // Start the script
    main();

})();