您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
将所有跳转到cnki.net的链接重定向到AH图书馆镜像站
// ==UserScript== // @name 知网安徽图书馆镜像 // @namespace http://github.com/no-teasy/cnki-ahlib-mirror // @version 1.4 // @description 将所有跳转到cnki.net的链接重定向到AH图书馆镜像站 // @author no-teasy [email protected] // @match *://*.ahlib.com/* // @match *://*.cnki.net/* // @grant none // @run-at document-start // @license MIT // ==/UserScript== (function() { const DEBUG = true; const SCRIPT_NAME = '[cnki-ahlib-mirror]'; function log(...args) { if (DEBUG) { console.log(SCRIPT_NAME, ...args); } } function warn(...args) { console.warn(SCRIPT_NAME, ...args); } function error(...args) { console.error(SCRIPT_NAME, ...args); } log('脚本开始运行,当前域名:', window.location.hostname); log('当前页面URL:', window.location.href); log('document readyState:', document.readyState); // 检测是否为知网域名 const isCnkiDomain = window.location.hostname.endsWith('cnki.net'); if(isCnkiDomain){ log("知网官方域名,即将跳转") const url = processCNKILink(window.location.href) window.location = url.toString(); } // 检查是否为AH图书馆域名 const isAHLibDomain = window.location.hostname.endsWith('ahlib.com'); if (!isAHLibDomain) { log('非AH图书馆域名,脚本不生效'); return; } log('检测到AH图书馆域名,脚本生效'); // 处理URL中的cnki.net链接 function processCNKILink(url) { try { // 如果URL不包含cnki.net,直接返回 if (!url || typeof url !== 'string') { return url; } log('开始处理URL:', url); // 检查是否包含cnki.net if (!url.includes('cnki.net')) { log('URL不包含cnki.net:', url); return url; } log('检测到cnki链接:', url); // 处理相对链接和完整链接 let fullUrl = url; if (!url.startsWith('http')) { try { fullUrl = new URL(url, window.location.href).href; log('转换相对链接为完整链接:', url, '->', fullUrl); } catch (e) { log('相对链接转换失败:', url, e.message); return url; } } const urlObj = new URL(fullUrl); log('解析URL对象:', { hostname: urlObj.hostname, pathname: urlObj.pathname, search: urlObj.search }); if (urlObj.hostname.includes('cnki.net')) { // 将域名中的点替换为横线 const modifiedHostname = urlObj.hostname.replace(/\./g, '-'); // 构造新的URL const newUrl = `https://${modifiedHostname}-s.ycfw.ahlib.com${urlObj.pathname}${urlObj.search}${urlObj.hash}`; log('链接转换成功:', url, '->', newUrl); return newUrl; } else { log('URL不包含cnki.net域名:', url); } } catch (e) { error('URL解析错误:', e, '原始URL:', url); } return url; } // 处理单个元素的属性 function processElementAttribute(element, attribute) { const originalUrl = element.getAttribute(attribute); log('检查元素属性:', element.tagName, attribute, '值:', originalUrl); if (originalUrl && originalUrl.includes('cnki.net')) { const newUrl = processCNKILink(originalUrl); if (newUrl !== originalUrl) { element.setAttribute(attribute, newUrl); log('元素属性更新:', element.tagName, attribute, originalUrl, '->', newUrl); return true; // 表示已处理 } } return false; // 表示未处理 } // 处理所有可能包含链接的元素 function processAllLinks(description = '常规检查') { log('开始批量处理链接 -', description); const startTime = performance.now(); // 处理各种标签的链接属性 const selectors = [ { tag: 'a', attr: 'href' }, { tag: 'form', attr: 'action' }, { tag: 'img', attr: 'src' }, { tag: 'script', attr: 'src' }, { tag: 'iframe', attr: 'src' }, { tag: 'link', attr: 'href' }, { tag: 'object', attr: 'data' }, { tag: 'embed', attr: 'src' }, { tag: 'source', attr: 'src' }, { tag: 'video', attr: 'src' }, { tag: 'audio', attr: 'src' } ]; let processedCount = 0; let totalElements = 0; selectors.forEach(selector => { try { log('查询选择器:', `${selector.tag}[${selector.attr}*="cnki.net"]`); const elements = document.querySelectorAll(`${selector.tag}[${selector.attr}*="cnki.net"]`); totalElements += elements.length; log('找到元素数量:', elements.length, '标签:', selector.tag, '属性:', selector.attr); elements.forEach((element, index) => { log('处理第', index + 1, '个元素:', element.tagName, element.getAttribute(selector.attr)); if (processElementAttribute(element, selector.attr)) { processedCount++; } }); if (elements.length > 0) { log(`处理${selector.tag}标签${selector.attr}属性:`, elements.length, '个元素'); } } catch (e) { error(`处理${selector.tag}标签时出错:`, e); } }); const endTime = performance.now(); log('批量处理完成 -', description, '耗时:', (endTime - startTime).toFixed(2), 'ms,总元素数:', totalElements, '处理元素数:', processedCount); return processedCount; } // 实时处理新创建的元素 function processNewElement(element) { if (!element || element.nodeType !== 1) return false; // 处理元素自身的属性 const linkAttributes = ['href', 'src', 'action', 'data']; let processed = false; linkAttributes.forEach(attr => { if (element.hasAttribute && element.hasAttribute(attr)) { const attrValue = element.getAttribute(attr); if (attrValue && attrValue.includes('cnki.net')) { log('新元素包含cnki链接:', element.tagName, attr, attrValue); if (processElementAttribute(element, attr)) { processed = true; } } } }); // 递归处理子元素 if (element.children) { Array.from(element.children).forEach(child => { if (processNewElement(child)) { processed = true; } }); } return processed; } // 拦截window.open const originalOpen = window.open; window.open = function(url, ...args) { log('拦截window.open调用:', url); const processedUrl = processCNKILink(url); log('window.open重定向:', url, '->', processedUrl); return originalOpen.call(this, processedUrl, ...args); }; // 拦截location.assign和location.replace if (window.location) { const originalAssign = window.location.assign; const originalReplace = window.location.replace; window.location.assign = function(url) { log('拦截location.assign调用:', url); const processedUrl = processCNKILink(url); log('location.assign重定向:', url, '->', processedUrl); return originalAssign.call(this, processedUrl); }; window.location.replace = function(url) { log('拦截location.replace调用:', url); const processedUrl = processCNKILink(url); log('location.replace重定向:', url, '->', processedUrl); return originalReplace.call(this, processedUrl); }; } // 使用MutationObserver监听DOM变化 - 更智能的监听 const observer = new MutationObserver(function(mutations) { let processedElements = 0; let attributeChanges = 0; let nodeAdditions = 0; mutations.forEach(function(mutation) { // 处理属性变化 if (mutation.type === 'attributes') { attributeChanges++; const attrName = mutation.attributeName; if (attrName && ['href', 'src', 'action', 'data'].includes(attrName)) { const oldValue = mutation.oldValue; const newValue = mutation.target.getAttribute(attrName); log('属性变化检测:', mutation.target.tagName, attrName, '旧值:', oldValue, '新值:', newValue); if (newValue && newValue.includes('cnki.net') && (!oldValue || !oldValue.includes('ycfw.ahlib.com'))) { log('检测到需要处理的属性变化:', mutation.target.tagName, attrName, oldValue, '->', newValue); processElementAttribute(mutation.target, attrName); processedElements++; } } } // 处理新增节点 mutation.addedNodes.forEach(function(node) { if (node.nodeType === 1) { // 元素节点 nodeAdditions++; log('检测到新增节点:', node.tagName || node.nodeName); if (processNewElement(node)) { processedElements++; } } }); }); if (processedElements > 0 || attributeChanges > 0 || nodeAdditions > 0) { log('MutationObserver处理完成 - 属性变化:', attributeChanges, '节点新增:', nodeAdditions, '处理元素数:', processedElements); } }); // 启动观察器 function startObserving() { observer.observe(document.documentElement, { childList: true, subtree: true, attributes: true, attributeOldValue: true, attributeFilter: ['href', 'src', 'action', 'data'] }); log('开始监听DOM变化'); } // 页面不同阶段的处理 log('注册事件监听器...'); // 立即开始观察(即使DOM未加载完成) startObserving(); if (document.readyState === 'loading') { // DOM还在加载中 log('DOM正在加载中,等待DOMContentLoaded'); document.addEventListener('DOMContentLoaded', function() { log('DOMContentLoaded事件触发'); setTimeout(() => processAllLinks('DOMContentLoaded'), 100); }); } else if (document.readyState === 'interactive') { // DOM已加载但资源还在加载 log('DOM已交互状态'); setTimeout(() => processAllLinks('Interactive状态'), 100); } else { // DOM已完成加载 log('DOM已完成加载'); setTimeout(() => processAllLinks('Complete状态'), 100); } // 页面加载完成后再次处理(确保所有内容都处理到) window.addEventListener('load', function() { log('页面load事件触发,再次处理所有链接'); setTimeout(() => processAllLinks('Load事件'), 500); // 执行测试 setTimeout(testSpecificLink, 1000); }); // 定期检查(作为保险机制) const intervalId = setInterval(function() { const count = processAllLinks('定时检查'); if (count > 0) { log('定时检查处理了', count, '个链接'); } }, 5000); log('启动定时检查,间隔5秒'); // 页面卸载时清理 window.addEventListener('beforeunload', function() { log('页面即将卸载,清理资源'); observer.disconnect(); clearInterval(intervalId); }); // 立即执行一次检查 setTimeout(() => processAllLinks('立即检查'), 1000); setTimeout(testSpecificLink, 2000); log('脚本初始化完成'); })();