Greasy Fork

Greasy Fork is available in English.

拷貝漫畫

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

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

(function() {
    'use strict';

    const SCRIPT_START_DELAY = 1000;// 目錄頁面腳本開始延遲時間(毫秒)
    const CHAPTER_PAGE_START_DELAY = 1000;// 章節頁面腳本開始延遲時間(毫秒)
    const LINK_CLEAN_DELAY = 2000;// 目錄頁面清理連結延遲時間(毫秒)
    const visitedPagesKey = 'visitedPages';
    const listPrefix = 'unvisitedLinks_';
    const scriptDisabledKey = 'copyMangaCleanerDisabled';
    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) {
        async function cleanLinks() {
            const links = document.querySelectorAll('a[href*="/chapter/"]:not([href*="#"])');
            const visitedPages = new Set(JSON.parse(localStorage.getItem(visitedPagesKey) || '[]'));
            for (const link of links) {
                const originalHref = link.href;
                link.href = link.href.replace(/-(?=[^/]*$)/g, '');
                const hashBuffer = await sha256(link.href);
                if (!hashBuffer) return false;
                const hashBase64 = arrayBufferToBase64(hashBuffer);
                if (visitedPages.has(hashBase64)) {
                    link.style.color = 'red';
                } else {
                    link.style.color = 'green';
                }
            }
            return true;
        }

        async function processUnvisitedLinks() {
            const currentPageUrl = window.location.href;
            const listKey = listPrefix + currentPageUrl;
            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 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];
                const newUnvisitedLinks = [targetLink, currentPageUrl];
                localStorage.setItem(listKey, JSON.stringify(newUnvisitedLinks));
                localStorage.setItem(visitedPagesKey, JSON.stringify([...visitedPages]));
                showStatus('已儲存連結,正在跳轉...');
                window.location.href = targetLink;
            } else {
                showStatus('沒有未訪問的連結');
            }
        }

        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) || '[]'));
                for (const link of links) {
                    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\/.*/, '');
                localStorage.removeItem(listKey);
                showStatus('已清除當前隊列!');
            });
            document.body.appendChild(clearButton);
        }

        function addDisableLinksButton() {
            const disableButton = document.createElement('button');
            disableButton.textContent = '停用連結';
            disableButton.style.position = 'fixed';
            disableButton.style.top = '60px';
            disableButton.style.left = '190px';
            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() {
            addClearHistoryButton();
            addClearQueueButton();
            addDisableLinksButton();
            setTimeout(async () => {
                const success = await cleanLinks();
                if (!success) {
                    showStatus('清理連結失敗,停止腳本');
                    return;
                }
                processUnvisitedLinks();
            }, LINK_CLEAN_DELAY);
        }

        setTimeout(runScript, SCRIPT_START_DELAY);
    }

    if (isChapterPage) {
        function runScript() {
            const isDisabled = localStorage.getItem(scriptDisabledKey) === 'true';
            if (!isDisabled) {
                const parentUrl = window.location.href.replace(/\/chapter\/.*/, '');
                window.location.href = parentUrl;
            } else {
                showStatus('腳本當前已停用');
            }
        }

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