Greasy Fork is available in English.
清理符號,檢查連結訪問狀態,開啟未訪問的連結
当前为
// ==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);
})();