Greasy Fork

Greasy Fork is available in English.

拷貝漫畫

清理符號,檢查連結訪問狀態,開啟未訪問的連結,並在章節頁面自動處理

当前为 2025-10-22 提交的版本,查看 最新版本

// ==UserScript==
// @name         拷貝漫畫
// @namespace    http://tampermonkey.net/
// @version      16.6
// @description  清理符號,檢查連結訪問狀態,開啟未訪問的連結,並在章節頁面自動處理
// @match        https://mangacopy.com/comic/*
// @grant        none
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    const SCRIPT_START_DELAY = 3000;  // 目錄頁面腳本開始延遲時間(毫秒)
    const CHAPTER_PAGE_START_DELAY = 1000;  // 章節頁面腳本開始延遲時間(毫秒)
    const PROCESSING_LOCK_TIMEOUT = 5000;  // 處理鎖定超時時間(毫秒)
    const QUEUE_CHECK_INTERVAL = 1000;  // 隊列檢查間隔(毫秒)
    const NAVIGATION_DELAY = 500;  // 頁面跳轉延遲時間(毫秒)
    const LIST_ADD_DELAY = 200;  // 新增清單後的延遲時間(毫秒)
    const visitedPagesKey = 'visitedPages';
    const listPrefix = 'unvisitedLinks_';
    const queueKey = 'processingQueue';
    const scriptEnabledKey = 'scriptEnabled';
    const scriptDisabledKey = 'copyMangaCleanerDisabled';
    const processingLockKey = 'processingLock';
    const isChapterPage = window.location.href.includes('/chapter/');

    function showStatus(message) {
        let statusBar = document.getElementById('status-bar');
        if (!statusBar) {
            statusBar = document.createElement('div');
            statusBar.id = 'status-bar';
            statusBar.style.position = 'fixed';
            statusBar.style.top = '10px';
            statusBar.style.left = '10px';
            statusBar.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
            statusBar.style.color = 'white';
            statusBar.style.padding = '10px';
            statusBar.style.borderRadius = '5px';
            statusBar.style.zIndex = '9999';
            document.body.appendChild(statusBar);
        }
        statusBar.textContent = message;
    }

    async function sha256(message) {
        try {
            const msgBuffer = new TextEncoder().encode(message);
            const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
            return hashBuffer;
        } catch (error) {
            console.error('哈希生成失敗:', error);
            return null;
        }
    }

    function arrayBufferToBase64(buffer) {
        let binary = '';
        const bytes = new Uint8Array(buffer);
        for (let i = 0; i < bytes.byteLength; i++) {
            binary += String.fromCharCode(bytes[i]);
        }
        return btoa(binary);
    }

    if (!isChapterPage) {
        let scriptEnabled = localStorage.getItem(scriptEnabledKey) !== 'false';

        function cleanLinks() {
            const links = document.querySelectorAll('a[href*="/chapter/"]:not([href*="#"])');
            const visitedPages = new Set(JSON.parse(localStorage.getItem(visitedPagesKey) || '[]'));
            links.forEach(async link => {
                const originalHref = link.href;
                link.href = link.href.replace(/-(?=[^/]*$)/g, '');
                const hashBuffer = await sha256(link.href);
                if (!hashBuffer) return;
                const hashBase64 = arrayBufferToBase64(hashBuffer);
                if (visitedPages.has(hashBase64)) {
                    link.style.color = 'red';
                } else {
                    link.style.color = 'green';
                }
            });
        }

        async function checkAndStoreUnvisitedLinks() {
            if (!scriptEnabled) return;
            const currentPageUrl = window.location.href;
            const listKey = listPrefix + currentPageUrl;
            let queue = JSON.parse(localStorage.getItem(queueKey) || '[]');
            const unvisitedLinks = JSON.parse(localStorage.getItem(listKey) || '[]');

            if (queue.includes(listKey) && unvisitedLinks.length === 0) {
                localStorage.removeItem(listKey);
                queue = queue.filter(item => item !== listKey);
                localStorage.setItem(queueKey, JSON.stringify(queue));
                showStatus('清單已清理');
                return;
            }

            if (!queue.includes(listKey)) {
                const visitedPages = new Set(JSON.parse(localStorage.getItem(visitedPagesKey) || '[]'));
                const allLinks = Array.from(document.querySelectorAll('a[href*="/chapter/"]:not([href*="#"])'));
                const tableDefaultRightDivs = document.querySelectorAll('div.table-default-right');
                let links = [];

                if (tableDefaultRightDivs.length > 0) {
                    links = Array.from(tableDefaultRightDivs[0].querySelectorAll('a[href*="/chapter/"]:not([href*="#"])'));
                } else {
                    links = allLinks;
                }

                const newUnvisitedLinks = [];
                const allUnvisited = [];

                for (const link of links) {
                    const hashBuffer = await sha256(link.href);
                    if (!hashBuffer) continue;
                    const hashBase64 = arrayBufferToBase64(hashBuffer);
                    if (!visitedPages.has(hashBase64)) {
                        allUnvisited.push(link.href);
                        visitedPages.add(hashBase64);
                    }
                }

                if (allUnvisited.length > 0) {
                    let targetLink = allUnvisited[0];
                    newUnvisitedLinks.push(targetLink);
                    newUnvisitedLinks.push(currentPageUrl);
                    localStorage.setItem(listKey, JSON.stringify(newUnvisitedLinks));
                    localStorage.setItem(visitedPagesKey, JSON.stringify([...visitedPages]));
                    queue.push(listKey);
                    localStorage.setItem(queueKey, JSON.stringify(queue));
                    showStatus('已加入隊列,等待' + (LIST_ADD_DELAY / 1000) + '秒...');
                    await new Promise(resolve => setTimeout(resolve, LIST_ADD_DELAY));
                } else {
                    return;
                }
            }

            const now = Date.now();
            const lock = JSON.parse(localStorage.getItem(processingLockKey) || 'null');
            if (lock && now - lock.timestamp < PROCESSING_LOCK_TIMEOUT) {
                showStatus('等待其他分頁處理完成...');
                setTimeout(checkAndStoreUnvisitedLinks, QUEUE_CHECK_INTERVAL);
                return;
            }

            if (queue[0] === listKey) {
                localStorage.setItem(processingLockKey, JSON.stringify({
                    timestamp: now,
                    listKey: listKey
                }));
                const storedLinks = JSON.parse(localStorage.getItem(listKey) || '[]');

                if (storedLinks.length === 0) {
                    localStorage.removeItem(listKey);
                    queue = queue.filter(item => item !== listKey);
                    localStorage.setItem(queueKey, JSON.stringify(queue));
                    localStorage.removeItem(processingLockKey);
                    showStatus('清單已清理');
                    return;
                }

                if (storedLinks[0] === currentPageUrl) {
                    localStorage.removeItem(listKey);
                    queue = queue.filter(item => item !== listKey);
                    localStorage.setItem(queueKey, JSON.stringify(queue));
                    localStorage.removeItem(processingLockKey);
                    showStatus('沒有更新');
                    return;
                }

                if (storedLinks.length > 0 && storedLinks[0]) {
                    const linkToNavigate = storedLinks[0];
                    const updatedQueue = queue.filter(item => item !== listKey);
                    localStorage.setItem(queueKey, JSON.stringify(updatedQueue));
                    setTimeout(() => {
                        localStorage.removeItem(listKey);
                        localStorage.removeItem(processingLockKey);
                        window.location.href = linkToNavigate;
                    }, NAVIGATION_DELAY);
                } else {
                    showStatus('錯誤:無有效連結');
                    localStorage.removeItem(processingLockKey);
                }
            } else {
                showStatus('正在排隊中...');
                setTimeout(checkAndStoreUnvisitedLinks, QUEUE_CHECK_INTERVAL);
            }
        }

        function addClearHistoryButton() {
            const clearButton = document.createElement('button');
            clearButton.textContent = '清除歷史';
            clearButton.style.position = 'fixed';
            clearButton.style.top = '60px';
            clearButton.style.left = '10px';
            clearButton.style.zIndex = '9999';
            clearButton.style.padding = '5px 10px';
            clearButton.style.backgroundColor = '#ff4444';
            clearButton.style.color = 'white';
            clearButton.style.border = 'none';
            clearButton.style.borderRadius = '5px';
            clearButton.style.cursor = 'pointer';
            clearButton.addEventListener('click', async () => {
                const links = Array.from(document.querySelectorAll('a[href*="/chapter/"]:not([href*="#"])'));
                const visitedPages = new Set(JSON.parse(localStorage.getItem(visitedPagesKey) || '[]'));
                await Promise.all(links.map(async link => {
                    const hashBuffer = await sha256(link.href);
                    if (!hashBuffer) return;
                    const hashBase64 = arrayBufferToBase64(hashBuffer);
                    if (visitedPages.has(hashBase64)) {
                        visitedPages.delete(hashBase64);
                    }
                }));
                localStorage.setItem(visitedPagesKey, JSON.stringify([...visitedPages]));
                showStatus('已清除當前頁面的連結記錄!');
            });
            document.body.appendChild(clearButton);
        }

        function addClearQueueButton() {
            const clearButton = document.createElement('button');
            clearButton.textContent = '清除隊列';
            clearButton.style.position = 'fixed';
            clearButton.style.top = '60px';
            clearButton.style.left = '100px';
            clearButton.style.zIndex = '9999';
            clearButton.style.padding = '5px 10px';
            clearButton.style.backgroundColor = '#ffaa44';
            clearButton.style.color = 'white';
            clearButton.style.border = 'none';
            clearButton.style.borderRadius = '5px';
            clearButton.style.cursor = 'pointer';
            clearButton.addEventListener('click', () => {
                const listKey = listPrefix + window.location.href.replace(/\/chapter\/.*/, '');
                const queue = JSON.parse(localStorage.getItem(queueKey) || '[]');
                localStorage.removeItem(listKey);
                const updatedQueue = queue.filter(item => item !== listKey);
                localStorage.setItem(queueKey, JSON.stringify(updatedQueue));
                showStatus('已清除當前隊列!');
            });
            document.body.appendChild(clearButton);
        }

        function addToggleButton() {
            const toggleButton = document.createElement('button');
            toggleButton.textContent = scriptEnabled ? '停用' : '啟用';
            toggleButton.style.position = 'fixed';
            toggleButton.style.top = '60px';
            toggleButton.style.left = '190px';
            toggleButton.style.zIndex = '9999';
            toggleButton.style.padding = '5px 10px';
            toggleButton.style.backgroundColor = scriptEnabled ? '#ff4444' : '#44aa44';
            toggleButton.style.color = 'white';
            toggleButton.style.border = 'none';
            toggleButton.style.borderRadius = '5px';
            toggleButton.style.cursor = 'pointer';
            toggleButton.addEventListener('click', () => {
                scriptEnabled = !scriptEnabled;
                localStorage.setItem(scriptEnabledKey, scriptEnabled);
                toggleButton.textContent = scriptEnabled ? '停用' : '啟用';
                toggleButton.style.backgroundColor = scriptEnabled ? '#ff4444' : '#44aa44';
                showStatus(scriptEnabled ? '腳本已啟用' : '腳本已停用');
                if (scriptEnabled) {
                    checkAndStoreUnvisitedLinks();
                }
            });
            document.body.appendChild(toggleButton);
        }

        function addDisableLinksButton() {
            const disableButton = document.createElement('button');
            disableButton.textContent = '停用連結';
            disableButton.style.position = 'fixed';
            disableButton.style.top = '60px';
            disableButton.style.left = '280px';
            disableButton.style.zIndex = '9999';
            disableButton.style.padding = '5px 10px';
            disableButton.style.backgroundColor = '#4444ff';
            disableButton.style.color = 'white';
            disableButton.style.border = 'none';
            disableButton.style.borderRadius = '5px';
            disableButton.style.cursor = 'pointer';
            disableButton.addEventListener('click', () => {
                const newState = localStorage.getItem(scriptDisabledKey) !== 'true';
                localStorage.setItem(scriptDisabledKey, newState.toString());
                showStatus(newState ? '章節頁面腳本已停用' : '章節頁面腳本已啟用');
            });
            document.body.appendChild(disableButton);
        }

        function runScript() {
            cleanLinks();
            addClearHistoryButton();
            addClearQueueButton();
            addToggleButton();
            addDisableLinksButton();
            if (scriptEnabled) {
                checkAndStoreUnvisitedLinks();
            } else {
                showStatus('腳本當前已停用');
            }
        }

        setTimeout(runScript, SCRIPT_START_DELAY);
    }

    if (isChapterPage) {
        function getUnvisitedLinks() {
            const listKey = listPrefix + window.location.href.replace(/\/chapter\/.*/, '');
            return JSON.parse(localStorage.getItem(listKey) || '[]');
        }

        function openNextLink(unvisitedLinks) {
            if (unvisitedLinks.length > 0) {
                const nextLink = unvisitedLinks.shift();
                const listKey = listPrefix + window.location.href.replace(/\/chapter\/.*/, '');
                localStorage.setItem(listKey, JSON.stringify(unvisitedLinks));

                if (unvisitedLinks.length === 0) {
                    const parentUrl = window.location.href.replace(/\/chapter\/.*/, '');
                    showStatus('所有連結已處理完畢,返回目錄頁。');
                    setTimeout(() => {
                        window.location.href = parentUrl;
                    }, NAVIGATION_DELAY);
                } else {
                    showStatus('正在開啟下一個連結...');
                    setTimeout(() => {
                        window.location.href = nextLink;
                    }, NAVIGATION_DELAY);
                }
            }
        }

        function runScript() {
            const isDisabled = localStorage.getItem(scriptDisabledKey) === 'true';
            if (!isDisabled) {
                const unvisitedLinks = getUnvisitedLinks();
                if (unvisitedLinks.length > 0) {
                    openNextLink(unvisitedLinks);
                } else {
                    const parentUrl = window.location.href.replace(/\/chapter\/.*/, '');
                    showStatus('沒有未訪問的連結,返回目錄頁。');
                    setTimeout(() => {
                        window.location.href = parentUrl;
                    }, NAVIGATION_DELAY);
                }
            } else {
                showStatus('腳本當前已停用');
            }
        }

        setTimeout(runScript, CHAPTER_PAGE_START_DELAY);
    }
})();