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