Greasy Fork is available in English.
清理符號,檢查連結訪問狀態,開啟未訪問的連結,並在章節頁面自動處理
当前为
// ==UserScript==
// @name 拷貝漫畫
// @namespace http://tampermonkey.net/
// @version 16.2
// @description 清理符號,檢查連結訪問狀態,開啟未訪問的連結,並在章節頁面自動處理
// @match https://mangacopy.com/comic/*
// @grant none
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// ===== 時間參數設定 =====
const SCRIPT_START_DELAY = 3000; // 目錄頁面腳本開始延遲時間(毫秒)
const CHAPTER_PAGE_START_DELAY = 1000; // 章節頁面腳本開始延遲時間(毫秒)
const PROCESSING_LOCK_TIMEOUT = 5000; // 處理鎖定超時時間(毫秒)
const QUEUE_CHECK_INTERVAL = 1000; // 隊列檢查間隔(毫秒)
const NAVIGATION_DELAY = 200; // 頁面跳轉延遲時間(毫秒)
const LIST_ADD_DELAY = 200; // 新增清單後的延遲時間(毫秒)
// ===== 常量設定 =====
const visitedPagesKey = 'visitedPages';
const listPrefix = 'unvisitedLinks_';
const queueKey = 'processingQueue';
const scriptEnabledKey = 'scriptEnabled';
const scriptDisabledKey = 'copyMangaCleanerDisabled';
const processingLockKey = 'processingLock';
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) {
let scriptEnabled = localStorage.getItem(scriptEnabledKey) !== 'false';
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';
}
});
}
async function checkAndStoreUnvisitedLinks() {
if (!scriptEnabled) return;
const currentPageUrl = window.location.href;
const listKey = listPrefix + currentPageUrl;
let queue = JSON.parse(localStorage.getItem(queueKey) || '[]');
const unvisitedLinks = JSON.parse(localStorage.getItem(listKey) || '[]');
if (queue.includes(listKey) && unvisitedLinks.length === 0) {
localStorage.removeItem(listKey);
queue = queue.filter(item => item !== listKey);
localStorage.setItem(queueKey, JSON.stringify(queue));
showStatus('清單已清理');
return;
}
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 = [];
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) {
// 16.2: 選取倒數第二個未訪問連結
let targetLink;
if (allUnvisited.length >= 2) {
targetLink = allUnvisited[allUnvisited.length - 2];
} else {
// 如果只有一個未訪問連結,則選取第一個(即最後一個)
targetLink = allUnvisited[allUnvisited.length - 1];
}
newUnvisitedLinks.push(targetLink);
newUnvisitedLinks.push(currentPageUrl);
localStorage.setItem(listKey, JSON.stringify(newUnvisitedLinks));
localStorage.setItem(visitedPagesKey, JSON.stringify([...visitedPages]));
queue.push(listKey);
localStorage.setItem(queueKey, JSON.stringify(queue));
showStatus('已加入隊列,等待' + (LIST_ADD_DELAY / 1000) + '秒...');
await new Promise(resolve => setTimeout(resolve, LIST_ADD_DELAY));
} else {
return;
}
}
const now = Date.now();
const lock = JSON.parse(localStorage.getItem(processingLockKey) || 'null');
if (lock && now - lock.timestamp < PROCESSING_LOCK_TIMEOUT) {
showStatus('等待其他分頁處理完成...');
setTimeout(checkAndStoreUnvisitedLinks, QUEUE_CHECK_INTERVAL);
return;
}
if (queue[0] === listKey) {
localStorage.setItem(processingLockKey, JSON.stringify({
timestamp: now,
listKey: listKey
}));
const storedLinks = JSON.parse(localStorage.getItem(listKey) || '[]');
if (storedLinks.length === 0) {
localStorage.removeItem(listKey);
queue = queue.filter(item => item !== listKey);
localStorage.setItem(queueKey, JSON.stringify(queue));
localStorage.removeItem(processingLockKey);
showStatus('清單已清理');
return;
}
if (storedLinks[0] === currentPageUrl) {
localStorage.removeItem(listKey);
queue = queue.filter(item => item !== listKey);
localStorage.setItem(queueKey, JSON.stringify(queue));
localStorage.removeItem(processingLockKey);
showStatus('沒有更新');
return;
}
if (storedLinks.length > 0 && storedLinks[0]) {
// 16.2: 跳轉前清除 newUnvisitedLinks
// 這邊 storedLinks[0] 就是要跳轉的連結
const linkToNavigate = storedLinks[0];
// 清除 localStorage 中的 listKey (相當於清除 newUnvisitedLinks)
localStorage.removeItem(listKey);
// 從 queue 中移除 listKey
queue = queue.filter(item => item !== listKey);
localStorage.setItem(queueKey, JSON.stringify(queue));
// 清除鎖定
localStorage.removeItem(processingLockKey);
showStatus('正在處理連結...');
setTimeout(() => {
window.location.href = linkToNavigate;
}, NAVIGATION_DELAY);
} else {
showStatus('錯誤:無有效連結');
localStorage.removeItem(processingLockKey);
}
} else {
showStatus('正在排隊中...');
setTimeout(checkAndStoreUnvisitedLinks, QUEUE_CHECK_INTERVAL);
}
}
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 addDisableLinksButton() {
const disableButton = document.createElement('button');
disableButton.textContent = '停用連結';
disableButton.style.position = 'fixed';
disableButton.style.top = '60px';
disableButton.style.left = '280px';
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() {
cleanLinks();
addClearHistoryButton();
addClearQueueButton();
addToggleButton();
addDisableLinksButton();
if (scriptEnabled) {
checkAndStoreUnvisitedLinks();
} else {
showStatus('腳本當前已停用');
}
}
setTimeout(runScript, SCRIPT_START_DELAY);
}
// ===== 章節頁面邏輯 =====
if (isChapterPage) {
function getUnvisitedLinks() {
const listKey = listPrefix + window.location.href.replace(/\/chapter\/.*/, '');
return JSON.parse(localStorage.getItem(listKey) || '[]');
}
function openNextLink(unvisitedLinks) {
if (unvisitedLinks.length > 0) {
const nextLink = unvisitedLinks.shift();
const listKey = listPrefix + window.location.href.replace(/\/chapter\/.*/, '');
localStorage.setItem(listKey, JSON.stringify(unvisitedLinks));
if (unvisitedLinks.length === 0) {
const parentUrl = window.location.href.replace(/\/chapter\/.*/, '');
showStatus('所有連結已處理完畢,返回目錄頁。');
setTimeout(() => {
window.location.href = parentUrl;
}, NAVIGATION_DELAY);
} else {
showStatus('正在開啟下一個連結...');
setTimeout(() => {
window.location.href = nextLink;
}, NAVIGATION_DELAY);
}
}
}
function runScript() {
const isDisabled = localStorage.getItem(scriptDisabledKey) === 'true';
if (!isDisabled) {
const unvisitedLinks = getUnvisitedLinks();
if (unvisitedLinks.length > 0) {
openNextLink(unvisitedLinks);
} else {
const parentUrl = window.location.href.replace(/\/chapter\/.*/, '');
showStatus('沒有未訪問的連結,返回目錄頁。');
setTimeout(() => {
window.location.href = parentUrl;
}, NAVIGATION_DELAY);
}
} else {
showStatus('腳本當前已停用');
}
}
setTimeout(runScript, CHAPTER_PAGE_START_DELAY);
}
})();