// ==UserScript== // @name GitHub镜像自动切换 // @namespace https://github.com/ // @version 1.0 // @description 自动将页面中的GitHub链接替换为可用的镜像地址 // @author richy430 // @match *://*/* // @exclude *://github.com/* // @exclude *://*.github.com/* // @exclude *://bgithub.xyz/* // @exclude *://kkgithub.com/* // @exclude *://gitclone.com/* // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @grant GM_notification // @run-at document-end // ==/UserScript== (function () { 'use strict'; // 配置管理 const CONFIG = { DEFAULT_MIRROR: 'bgithub', MIRRORS: { 'bgithub': { name: 'bGitHub', url: 'https://bgithub.xyz', type: 'full', status: '✅' }, 'github1s': { name: 'github1s', url: 'https://github1s.com', type: 'full', status: '✅' }, 'kkgithub': { name: 'KGitHub', url: 'https://kkgithub.com', type: 'full', status: '✅' }, 'gitclone': { name: 'GitClone', url: 'https://gitclone.com', type: 'full', status: '✅' }, 'githubur1': { name: 'GitHub UR1', url: 'https://github.ur1.fun', type: 'full', status: '✅' }, 'githubzh': { name: 'GitHub中文网', url: 'https://www.github-zh.com', type: 'full', status: '✅' }, 'ghproxy': { name: 'GHProxy', url: 'https://ghproxy.com', type: 'proxy', status: '✅' } }, REPLACEMENT_PATTERNS: [ { regex: /https?:\/\/github\.com\//g, replacement: '{mirror}/' }, { regex: /https?:\/\/www\.github\.com\//g, replacement: '{mirror}/' }, { regex: /https?:\/\/raw\.githubusercontent\.com\//g, replacement: '{mirror}/raw.githubusercontent.com/' }, { regex: /https?:\/\/gist\.github\.com\//g, replacement: '{mirror}/gist.github.com/' }, { regex: /https?:\/\/api\.github\.com\//g, replacement: '{mirror}/api.github.com/' }, { regex: /https?:\/\/codeload\.github\.com\//g, replacement: '{mirror}/codeload.github.com/' } ] }; // 初始化配置 let currentMirror = GM_getValue('github_mirror', CONFIG.DEFAULT_MIRROR); let replacementCount = 0; // 工具函数 function log(message, type = 'info') { const colors = { info: '#2196F3', success: '#4CAF50', warning: '#FF9800', error: '#F44336' }; console.log(`%c[GitHub Mirror] ${message}`, `color: ${colors[type]}; font-weight: bold;`); } function getMirrorConfig() { return CONFIG.MIRRORS[currentMirror] || CONFIG.MIRRORS[CONFIG.DEFAULT_MIRROR]; } function replaceGitHubUrl(url) { if (!url || typeof url !== 'string') return url; const mirrorConfig = getMirrorConfig(); let newUrl = url; CONFIG.REPLACEMENT_PATTERNS.forEach(pattern => { if (mirrorConfig.type === 'proxy') { // 代理模式:在URL前添加代理前缀 if (pattern.regex.test(newUrl)) { newUrl = mirrorConfig.url + '/' + newUrl; } } else { // 镜像模式:替换域名 newUrl = newUrl.replace(pattern.regex, pattern.replacement.replace('{mirror}', mirrorConfig.url)); } }); return newUrl !== url ? newUrl : url; } // 替换链接 function replaceLinks() { const startTime = performance.now(); // 替换所有a标签的href document.querySelectorAll('a[href*="github.com"]').forEach(link => { const originalHref = link.href; const newHref = replaceGitHubUrl(originalHref); if (newHref !== originalHref) { link.href = newHref; link.dataset.githubOriginalHref = originalHref; link.title = `原始地址: ${originalHref}\n镜像地址: ${newHref}`; replacementCount++; log(`替换链接: ${originalHref} → ${newHref}`, 'success'); } }); // 替换文本中的GitHub链接 const textNodes = getTextNodes(document.body); textNodes.forEach(node => { const originalText = node.textContent; const newText = replaceGitHubUrlInText(originalText); if (newText !== originalText) { node.textContent = newText; replacementCount++; log(`替换文本中的链接: ${originalText} → ${newText}`, 'success'); } }); const endTime = performance.now(); if (replacementCount > 0) { log(`替换完成,共处理 ${replacementCount} 个链接,耗时 ${(endTime - startTime).toFixed(2)}ms`, 'info'); showNotification(`已替换 ${replacementCount} 个GitHub链接为 ${getMirrorConfig().name}`); } } function getTextNodes(element) { const nodes = []; const walker = document.createTreeWalker( element, NodeFilter.SHOW_TEXT, null, false ); let node; while ((node = walker.nextNode())) { if (node.textContent.includes('github.com')) { nodes.push(node); } } return nodes; } function replaceGitHubUrlInText(text) { const mirrorConfig = getMirrorConfig(); let newText = text; CONFIG.REPLACEMENT_PATTERNS.forEach(pattern => { if (mirrorConfig.type === 'proxy') { newText = newText.replace(pattern.regex, match => { return mirrorConfig.url + '/' + match; }); } else { newText = newText.replace(pattern.regex, pattern.replacement.replace('{mirror}', mirrorConfig.url)); } }); return newText; } // 动态内容监听 function observeDynamicContent() { const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.addedNodes.length) { mutation.addedNodes.forEach((node) => { if (node.nodeType === 1) { // 元素节点 // 检查当前节点是否包含GitHub链接 if (node.outerHTML && node.outerHTML.includes('github.com')) { processNode(node); } // 检查子节点 node.querySelectorAll('a[href*="github.com"], [data*="github.com"], [style*="github.com"]').forEach(child => { processNode(child); }); } else if (node.nodeType === 3 && node.textContent.includes('github.com')) { // 文本节点 const newText = replaceGitHubUrlInText(node.textContent); if (newText !== node.textContent) { node.textContent = newText; replacementCount++; } } }); } }); }); // 监听整个文档的变化 observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['href', 'src', 'data-src', 'style', 'data'] }); log('已启动动态内容监听', 'info'); return observer; } function processNode(node) { // 处理a标签 if (node.tagName === 'A' && node.href) { const originalHref = node.href; const newHref = replaceGitHubUrl(originalHref); if (newHref !== originalHref) { node.href = newHref; node.dataset.githubOriginalHref = originalHref; replacementCount++; log(`动态替换链接: ${originalHref} → ${newHref}`, 'success'); } } // 处理其他属性中的GitHub链接 ['src', 'data-src', 'style', 'data'].forEach(attribute => { if (node.hasAttribute(attribute)) { const originalValue = node.getAttribute(attribute); const newValue = replaceGitHubUrl(originalValue); if (newValue !== originalValue) { node.setAttribute(attribute, newValue); replacementCount++; log(`动态替换${attribute}: ${originalValue} → ${newValue}`, 'success'); } } }); } // 通知系统 function showNotification(message, type = 'info') { if (typeof GM_notification === 'function') { GM_notification({ title: 'GitHub镜像替换', text: message, timeout: 5000 }); } else { // 备用通知方式 showCustomNotification(message, type); } } function showCustomNotification(message, type = 'info') { const notification = document.createElement('div'); notification.className = `github-mirror-notification github-mirror-${type}`; notification.style.cssText = ` position: fixed; top: 20px; right: 20px; padding: 15px 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 8px; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3); z-index: 999999; font-family: 'Microsoft YaHei', Arial, sans-serif; font-size: 14px; max-width: 300px; opacity: 0; transform: translateY(-20px); transition: all 0.3s ease; `; if (type === 'success') { notification.style.background = 'linear-gradient(135deg, #4CAF50 0%, #45a049 100%)'; } else if (type === 'warning') { notification.style.background = 'linear-gradient(135deg, #FF9800 0%, #f57c00 100%)'; } else if (type === 'error') { notification.style.background = 'linear-gradient(135deg, #F44336 0%, #d32f2f 100%)'; } notification.textContent = message; document.body.appendChild(notification); // 显示动画 setTimeout(() => { notification.style.opacity = '1'; notification.style.transform = 'translateY(0)'; }, 100); // 自动隐藏 setTimeout(() => { notification.style.opacity = '0'; notification.style.transform = 'translateY(-20px)'; setTimeout(() => { if (notification.parentNode) { notification.parentNode.removeChild(notification); } }, 300); }, 5000); } // 菜单命令 function registerMenuCommands() { // 切换镜像 Object.keys(CONFIG.MIRRORS).forEach(key => { const mirror = CONFIG.MIRRORS[key]; GM_registerMenuCommand( `${mirror.status} ${mirror.name}${key === currentMirror ? ' ✓' : ''}`, () => { currentMirror = key; GM_setValue('github_mirror', key); location.reload(); showNotification(`已切换到 ${mirror.name}`, 'success'); } ); }); // 重置计数 GM_registerMenuCommand('重置替换计数', () => { replacementCount = 0; showNotification('替换计数已重置', 'info'); }); // 显示统计 GM_registerMenuCommand('显示统计信息', () => { const mirrorConfig = getMirrorConfig(); showNotification( `当前镜像: ${mirrorConfig.name}\n` + `替换链接总数: ${replacementCount}\n` + `镜像类型: ${mirrorConfig.type === 'full' ? '完整镜像' : '代理加速'}`, 'info' ); }); } // 性能监控 function monitorPerformance() { const observer = observeDynamicContent(); // 页面加载完成后执行 window.addEventListener('load', () => { setTimeout(() => { replaceLinks(); log('页面加载完成,链接替换完成', 'info'); }, 100); }); // DOMContentLoaded时执行一次 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { setTimeout(() => { replaceLinks(); }, 50); }); } else { // 如果DOM已经加载完成,立即执行 setTimeout(() => { replaceLinks(); }, 50); } return observer; } // 初始化 function init() { log('GitHub镜像替换脚本已启动', 'info'); log(`当前使用镜像: ${getMirrorConfig().name} (${getMirrorConfig().url})`, 'info'); // 注册菜单命令 if (typeof GM_registerMenuCommand === 'function') { registerMenuCommands(); } // 启动性能监控和链接替换 const observer = monitorPerformance(); // 暴露一些API给控制台 if (typeof window !== 'undefined') { window.githubMirror = { getCurrentMirror: () => getMirrorConfig(), getReplacementCount: () => replacementCount, refresh: () => replaceLinks(), switchMirror: (key) => { if (CONFIG.MIRRORS[key]) { currentMirror = key; GM_setValue('github_mirror', key); location.reload(); } } }; } return observer; } // 启动脚本 const observer = init(); // 错误处理 window.addEventListener('error', (error) => { log(`脚本错误: ${error.message}`, 'error'); }); log('GitHub镜像替换脚本初始化完成', 'success'); })();