Greasy Fork

Greasy Fork is available in English.

拷貝漫畫

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==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);
    }
})();