Greasy Fork

Greasy Fork is available in English.

拷貝漫畫視覺化

清理符號,檢查連結訪問狀態,開啟未訪問的連結

当前为 2025-04-02 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         拷貝漫畫視覺化
// @namespace    http://tampermonkey.net/
// @version      13.1
// @description  清理符號,檢查連結訪問狀態,開啟未訪問的連結
// @match        https://mangacopy.com/comic/*
// @exclude      https://mangacopy.com/comic/*/chapter/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    const visitedPagesKey = 'visitedPages';
    const listPrefix = 'unvisitedLinks_';
    const queueKey = 'processingQueue';
    const scriptEnabledKey = 'scriptEnabled';

    // 檢查腳本啟用狀態
    let scriptEnabled = localStorage.getItem(scriptEnabledKey) !== 'false'; // 默認啟用

    // 生成 SHA-256 哈希
    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;
        }
    }

    // 將 ArrayBuffer 轉換為 Base64 字符串
    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);
    }

    // 清理連結中的 - 符號
    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'; // 未訪問的連結顯示為綠色
            }
        });
    }

    // 顯示狀態訊息
    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 checkAndStoreUnvisitedLinks() {
        if (!scriptEnabled) return;

        const currentPageUrl = window.location.href;
        const listKey = listPrefix + currentPageUrl;
        const queue = JSON.parse(localStorage.getItem(queueKey) || '[]');
        const unvisitedLinks = JSON.parse(localStorage.getItem(listKey) || '[]');

        // 如果 queue 中存在 listKey 且 unvisitedLinks 為空,則清理當前清單
        if (queue.includes(listKey) && unvisitedLinks.length === 0) {
            localStorage.removeItem(listKey);
            const updatedQueue = queue.filter(item => item !== listKey);
            localStorage.setItem(queueKey, JSON.stringify(updatedQueue));
            showStatus('清單已清理');
            return;
        }

        // 如果 queue 中不存在 listKey,則進入分析生成新的列表
        if (!queue.includes(listKey)) {
            const visitedPages = new Set(JSON.parse(localStorage.getItem(visitedPagesKey) || '[]'));
            const links = Array.from(document.querySelectorAll('a[href*="/chapter/"]:not([href*="#"])'));
            const newUnvisitedLinks = [];

            // 只加入第一個未訪問的連結
            let firstUnvisitedLinkFound = false;

            for (const link of links) {
                const hashBuffer = await sha256(link.href);
                if (!hashBuffer) continue;

                const hashBase64 = arrayBufferToBase64(hashBuffer);
                if (!visitedPages.has(hashBase64)) {
                    if (!firstUnvisitedLinkFound) {
                        newUnvisitedLinks.push(link.href);
                        firstUnvisitedLinkFound = true;
                    }
                    visitedPages.add(hashBase64);
                }
            }

            // 只有當找到未訪問連結時,才將當前目錄頁的 URL 加在 unvisitedLinks 的最後
            if (firstUnvisitedLinkFound) {
                newUnvisitedLinks.push(currentPageUrl);
            }

            // 僅在存在未訪問連結時儲存
            if (newUnvisitedLinks.length > 0) {
                localStorage.setItem(listKey, JSON.stringify(newUnvisitedLinks));
                localStorage.setItem(visitedPagesKey, JSON.stringify([...visitedPages]));
                queue.push(listKey);
                localStorage.setItem(queueKey, JSON.stringify(queue));
                showStatus('已加入隊列,等待15秒...');
                await new Promise(resolve => setTimeout(resolve, 15000));
            } else {
                return; // 無未訪問連結,不進行後續處理
            }
        }

        // 檢查是否輪到自己
        if (queue[0] === listKey) {
            const storedLinks = JSON.parse(localStorage.getItem(listKey) || '[]');

            // 新增空清單檢查
            if (storedLinks.length === 0) {
                localStorage.removeItem(listKey);
                const updatedQueue = queue.filter(item => item !== listKey);
                localStorage.setItem(queueKey, JSON.stringify(updatedQueue));
                showStatus('清單已清理');
                return;
            }

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

            // 確認連結存在後跳轉
            if (storedLinks.length > 0 && storedLinks[0]) {
                showStatus('正在處理連結...');
                setTimeout(() => {
                    window.location.href = storedLinks[0];
                }, 1000);
            } else {
                showStatus('錯誤:無有效連結');
            }
        } else {
            showStatus('正在排隊中...');
            setTimeout(checkAndStoreUnvisitedLinks, 5000);
        }
    }

    // 新增:清除歷史按鈕
    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 runScript() {
        cleanLinks();
        addClearHistoryButton();
        addClearQueueButton();
        addToggleButton();

        if (scriptEnabled) {
            checkAndStoreUnvisitedLinks();
        } else {
            showStatus('腳本當前已停用');
        }
    }

    // 延遲 2 秒執行以避免頁面未完全加載
    setTimeout(runScript, 2000);
})();