// ==UserScript== // @name GitHub镜像加速 // @namespace http://tampermonkey.net/ // @version 1.0.4 // @description 自动检测GitHub连通性并重定向到镜像站点,支持文件下载加速 // @author Your Name // @match *://github.com/* // @match *://raw.githubusercontent.com/* // @match *://*.github.com/* // @match *://www.githubstatus.com/* // @match https://github.* // @exclude *kkgithub.com* // @exclude *bgithub.xyz* // @exclude *gitclone.com* // @exclude *github.ur1.fun* // @run-at document-start // @inject-into page // @grant GM_setValue // @grant GM_getValue // @grant GM_xmlhttpRequest // @grant GM_notification // @grant GM_registerMenuCommand // @grant GM_openInTab // @grant GM_addStyle // @grant GM_getTab // @grant GM_saveTab // @grant unsafeWindow // @connect github.com // @connect raw.githubusercontent.com // @connect kkgithub.com // @connect bgithub.xyz // @connect gitclone.com // @connect github.ur1.fun // @connect moeyy.cn // @connect gh-proxy.com // @connect ghproxy.net // @connect ghp.ci // @connect toolwa.com // @connect ghproxy.homeboyc.cn // ==/UserScript== (function() { 'use strict'; // 立即检查URL是否为github.com域名 const hostname = window.location.hostname; const isGithubDomain = hostname === 'github.com' || hostname.endsWith('.github.com') || (hostname.includes('github') && !hostname.includes('kkgithub') && !hostname.includes('bgithub') && !hostname.includes('gitclone') && !hostname.includes('github.ur1.fun')); // 是否为错误页面的快速检测 const isErrorPage = typeof document !== 'undefined' && (document.title.includes('无法访问') || document.title.includes('can\'t be reached') || document.title.includes('timeout') || document.title.includes('timed out') || document.title.includes('ERR_') || document.title.includes('无法连接') || document.documentElement.innerHTML.includes('ERR_CONNECTION_TIMED_OUT') || document.documentElement.innerHTML.includes('无法访问此网站')); // 默认配置 const defaultConfig = { enabled: true, autoMode: true, lastCheck: 0, isGitHubAccessible: false, // 默认不可访问,安全起见 checkInterval: 5 * 60 * 1000, // 检查间隔,默认5分钟 selectedMirror: 'kkgithub.com', mirrors: [ { name: 'BGitHub', url: 'bgithub.xyz' }, { name: 'KKGitHub', url: 'kkgithub.com' }, { name: 'GitClone', url: 'gitclone.com/github.com' }, { name: 'GitHub加速下载', url: 'github.ur1.fun' } ], selectedFileProxy: 'https://moeyy.cn/gh-proxy/', fileProxies: [ { name: 'Moeyy', url: 'https://moeyy.cn/gh-proxy/' }, { name: 'GHProxy.com', url: 'https://gh-proxy.com/' }, { name: 'GHProxy.net', url: 'https://ghproxy.net/' }, { name: 'GHProxy.ci', url: 'https://ghp.ci/' }, { name: 'TOOLWA', url: 'http://toolwa.com/github/' }, { name: 'HomeBoyCi', url: 'https://ghproxy.homeboyc.cn/' } ] }; // 获取配置 let config = GM_getValue('config', defaultConfig); // 如果是GitHub域名,直接执行重定向逻辑 if (isGithubDomain) { // 检查是否是错误页面 if (isErrorPage) { // 错误页面直接重定向 redirectToMirror(); } else { // 普通页面走检测逻辑 tryRedirect(); } } // 尝试重定向 function tryRedirect() { // 只在启用状态下执行 if (!config.enabled) return; // 如果非自动模式,或者自动模式下GitHub不可访问,执行重定向 if (!config.autoMode || (config.autoMode && !config.isGitHubAccessible)) { redirectToMirror(); return; } // 在自动模式下,且没有明确判断GitHub不可访问时,进行快速检测 fastCheckAndRedirect(); } // 执行镜像重定向 function redirectToMirror() { try { const currentUrl = window.location.href; const url = new URL(currentUrl); // 检查是否已经是镜像站点 if (url.hostname === config.selectedMirror || url.hostname.includes(config.selectedMirror.split('/')[0])) { return; } // 构建新的镜像URL url.hostname = config.selectedMirror; // 使用replace以避免留在历史记录中 console.log("正在重定向到: " + url.toString()); window.location.replace(url.toString()); } catch (e) { console.error('重定向时出错:', e); } } // 快速检测GitHub可访问性并重定向 function fastCheckAndRedirect() { const now = Date.now(); // 如果近期检查过,使用缓存结果 if (now - config.lastCheck < config.checkInterval) { if (!config.isGitHubAccessible) { redirectToMirror(); } return; } // 更新检查时间 config.lastCheck = now; // 设置1秒超时 const timeoutId = setTimeout(() => { // 超时认为不可访问 config.isGitHubAccessible = false; GM_setValue('config', config); redirectToMirror(); }, 1000); // 尝试访问favicon const img = new Image(); img.onload = function() { clearTimeout(timeoutId); config.isGitHubAccessible = true; GM_setValue('config', config); // 可访问,不重定向 }; img.onerror = function() { clearTimeout(timeoutId); config.isGitHubAccessible = false; GM_setValue('config', config); redirectToMirror(); }; img.src = 'https://github.com/favicon.ico?' + new Date().getTime(); } // 检查GitHub可访问性 (用于其他操作) function checkGitHubAccessibility() { return new Promise((resolve) => { // 如果距离上次检查的时间不足checkInterval,则使用上次的结果 const now = Date.now(); if (now - config.lastCheck < config.checkInterval) { resolve(config.isGitHubAccessible); return; } // 更新上次检查时间 config.lastCheck = now; // 设置超时处理 const timeout = setTimeout(() => { config.isGitHubAccessible = false; GM_setValue('config', config); updateUI(); resolve(false); }, 2000); // 2秒超时 // 使用GM_xmlhttpRequest进行网络测试 GM_xmlhttpRequest({ method: 'HEAD', url: 'https://github.com/favicon.ico', timeout: 2000, onload: function(response) { clearTimeout(timeout); config.isGitHubAccessible = true; GM_setValue('config', config); updateUI(); console.log('GitHub 可访问,不需要使用镜像'); resolve(true); }, onerror: function(error) { clearTimeout(timeout); config.isGitHubAccessible = false; GM_setValue('config', config); updateUI(); console.log('GitHub 不可访问,将使用镜像', error); resolve(false); } }); }); } // 更新UI显示 function updateUI() { const status = document.getElementById('github-mirror-status'); if (status) { if (config.isGitHubAccessible) { status.textContent = 'GitHub 可正常访问'; status.className = 'github-mirror-status good'; } else { status.textContent = 'GitHub 无法访问,使用镜像'; status.className = 'github-mirror-status bad'; } } } // 添加状态显示元素 function addStatusElement() { try { // 尝试添加到Header const header = document.querySelector('.Header'); if (header && !document.getElementById('github-mirror-status')) { const status = document.createElement('div'); status.id = 'github-mirror-status'; status.className = 'github-mirror-status'; status.textContent = '检测中...'; header.appendChild(status); return; } // 如果没有找到Header,则创建一个悬浮元素 if (!document.getElementById('github-mirror-status') && document.body) { const status = document.createElement('div'); status.id = 'github-mirror-status'; status.className = 'github-mirror-status floating'; status.textContent = '检测中...'; document.body.appendChild(status); } } catch (e) { console.error('添加状态元素时出错:', e); } } // 修改下载链接 function modifyDownloadLinks() { try { // 查找所有指向GitHub资源的链接 const links = document.querySelectorAll('a[href*="github.com"], a[href*="raw.githubusercontent.com"], a[href*="releases/download"]'); links.forEach(link => { const href = link.getAttribute('href'); // 检查是否为文件下载链接 if (isFileDownloadLink(href) && config.selectedFileProxy) { // 使用文件代理替换链接 link.setAttribute('original-href', href); link.setAttribute('href', `${config.selectedFileProxy}${href}`); link.setAttribute('data-gh-proxy', 'true'); // 添加视觉提示 link.style.color = '#1e88e5'; } }); } catch (e) { console.error('修改下载链接时出错:', e); } } // 判断是否为文件下载链接 function isFileDownloadLink(url) { // 检查是否是常见的GitHub资源下载链接 const patterns = [ /github\.com\/.*\/releases\/download\//, /raw\.githubusercontent\.com\//, /github\.com\/.*\/archive\//, /github\.com\/.*\/blob\//, /codeload\.github\.com\// ]; return patterns.some(pattern => pattern.test(url)); } // 注册菜单命令 function registerMenuCommands() { try { // 启用/禁用开关 GM_registerMenuCommand( config.enabled ? '禁用GitHub镜像加速' : '启用GitHub镜像加速', function() { config.enabled = !config.enabled; GM_setValue('config', config); GM_notification({ text: config.enabled ? '已启用GitHub镜像加速' : '已禁用GitHub镜像加速', timeout: 3000 }); } ); // 自动模式开关 GM_registerMenuCommand( config.autoMode ? '关闭自动检测模式' : '开启自动检测模式', function() { config.autoMode = !config.autoMode; GM_setValue('config', config); GM_notification({ text: config.autoMode ? '已开启自动检测模式' : '已关闭自动检测模式', timeout: 3000 }); } ); // 立即检测 GM_registerMenuCommand('立即检测GitHub连通性', function() { checkGitHubAccessibility(); }); // 直接进入镜像站点 GM_registerMenuCommand('直接进入镜像站点', function() { const currentUrl = window.location.href; const url = new URL(currentUrl); url.hostname = config.selectedMirror; window.location.href = url.toString(); }); // 打开设置面板 GM_registerMenuCommand('打开设置面板', function() { openSettingsPanel(); }); } catch (e) { console.error('注册菜单时出错:', e); } } // 设置面板 function openSettingsPanel() { try { // 关闭已存在的面板 const existingPanel = document.getElementById('github-mirror-settings-panel'); if (existingPanel) { document.body.removeChild(existingPanel); return; } // 创建面板容器 const panel = document.createElement('div'); panel.id = 'github-mirror-settings-panel'; panel.className = 'github-mirror-settings-panel'; // 面板内容 panel.innerHTML = `

GitHub镜像加速设置

开启后,只在GitHub不可访问时自动切换到镜像站点

GitHub 当前状态: ${config.isGitHubAccessible ? '可访问' : '不可访问'}
`; // 添加到页面 document.body.appendChild(panel); // 添加事件监听 document.getElementById('close-panel').addEventListener('click', function() { document.body.removeChild(panel); }); document.getElementById('check-now-btn').addEventListener('click', async function() { this.textContent = '检测中...'; this.disabled = true; const isAccessible = await checkGitHubAccessibility(); this.textContent = '立即检测'; this.disabled = false; const indicator = document.querySelector('.status-indicator'); indicator.className = `status-indicator ${isAccessible ? 'good' : 'bad'}`; indicator.parentNode.querySelector('span').textContent = `GitHub 当前状态: ${isAccessible ? '可访问' : '不可访问'}`; }); document.getElementById('save-settings').addEventListener('click', function() { // 收集设置 config.enabled = document.getElementById('enable-toggle').checked; config.autoMode = document.getElementById('auto-mode-toggle').checked; config.selectedMirror = document.getElementById('mirror-select').value; config.selectedFileProxy = document.getElementById('file-proxy-select').value; config.checkInterval = parseInt(document.getElementById('check-interval').value) * 60 * 1000; // 保存设置 GM_setValue('config', config); // 提示 GM_notification({ text: '设置已保存', timeout: 3000 }); // 关闭面板 document.body.removeChild(panel); }); document.getElementById('reset-settings').addEventListener('click', function() { if (confirm('确定要重置所有设置到默认值吗?')) { config = defaultConfig; GM_setValue('config', config); GM_notification({ text: '设置已重置为默认值', timeout: 3000 }); document.body.removeChild(panel); } }); // 点击面板外关闭 panel.addEventListener('click', function(e) { if (e.target === panel) { document.body.removeChild(panel); } }); } catch (e) { console.error('打开设置面板时出错:', e); } } // 添加样式 function addStyles() { try { GM_addStyle(` .github-mirror-status { padding: 5px 10px; border-radius: 4px; margin-left: 10px; font-size: 12px; font-weight: 500; background-color: #6a737d; color: white; } .github-mirror-status.good { background-color: #2ea44f; } .github-mirror-status.bad { background-color: #d73a49; } .github-mirror-status.floating { position: fixed; top: 10px; right: 10px; z-index: 9999; box-shadow: 0 2px 5px rgba(0,0,0,0.2); } /* 设置面板样式 */ .github-mirror-settings-panel { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.5); z-index: 10000; display: flex; justify-content: center; align-items: center; } .github-mirror-settings-panel .panel-header, .github-mirror-settings-panel .panel-body, .github-mirror-settings-panel .panel-footer { background-color: white; width: 500px; padding: 15px; box-sizing: border-box; } .github-mirror-settings-panel .panel-header { border-radius: 8px 8px 0 0; border-bottom: 1px solid #eee; display: flex; justify-content: space-between; align-items: center; } .github-mirror-settings-panel .panel-header h2 { margin: 0; font-size: 18px; } .github-mirror-settings-panel .panel-body { max-height: 70vh; overflow-y: auto; } .github-mirror-settings-panel .panel-footer { border-radius: 0 0 8px 8px; border-top: 1px solid #eee; display: flex; justify-content: flex-end; gap: 10px; } .github-mirror-settings-panel .setting-group { margin-bottom: 15px; } .github-mirror-settings-panel label { display: block; margin-bottom: 5px; font-weight: 500; } .github-mirror-settings-panel select, .github-mirror-settings-panel input[type="number"] { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; margin-top: 4px; } .github-mirror-settings-panel .setting-description { font-size: 12px; color: #666; margin-top: 4px; } .github-mirror-settings-panel button { padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; background-color: #eee; } .github-mirror-settings-panel #close-panel { background: none; border: none; font-size: 24px; cursor: pointer; padding: 0; } .github-mirror-settings-panel #save-settings { background-color: #2ea44f; color: white; } .github-mirror-settings-panel .network-status { display: flex; align-items: center; gap: 10px; padding: 10px; background-color: #f6f8fa; border-radius: 4px; } .github-mirror-settings-panel .status-indicator { width: 12px; height: 12px; border-radius: 50%; background-color: #6a737d; } .github-mirror-settings-panel .status-indicator.good { background-color: #2ea44f; } .github-mirror-settings-panel .status-indicator.bad { background-color: #d73a49; } /* 开关样式 */ .github-mirror-settings-panel .switch-label { display: flex; justify-content: space-between; align-items: center; } .github-mirror-settings-panel .switch { position: relative; display: inline-block; width: 40px; height: 24px; } .github-mirror-settings-panel .switch input { opacity: 0; width: 0; height: 0; } .github-mirror-settings-panel .slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .4s; } .github-mirror-settings-panel .slider:before { position: absolute; content: ""; height: 16px; width: 16px; left: 4px; bottom: 4px; background-color: white; transition: .4s; } .github-mirror-settings-panel input:checked + .slider { background-color: #2ea44f; } .github-mirror-settings-panel input:checked + .slider:before { transform: translateX(16px); } .github-mirror-settings-panel .slider.round { border-radius: 24px; } .github-mirror-settings-panel .slider.round:before { border-radius: 50%; } `); } catch (e) { console.error('添加样式时出错:', e); } } // 初始化 async function init() { try { // 添加样式 addStyles(); // 注册菜单命令 registerMenuCommands(); // 添加状态显示 if (document.body) { addStatusElement(); } else { document.addEventListener('DOMContentLoaded', addStatusElement); } // 检查GitHub可访问性并更新UI await checkGitHubAccessibility(); // 修改下载链接(等待DOM加载完成) if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', modifyDownloadLinks); } else { modifyDownloadLinks(); } // 监听DOM变化 if (document.body) { const observer = new MutationObserver(function(mutations) { for (const mutation of mutations) { if (mutation.addedNodes.length) { modifyDownloadLinks(); } } }); observer.observe(document.body, { childList: true, subtree: true }); } else { document.addEventListener('DOMContentLoaded', () => { const observer = new MutationObserver(function(mutations) { for (const mutation of mutations) { if (mutation.addedNodes.length) { modifyDownloadLinks(); } } }); observer.observe(document.body, { childList: true, subtree: true }); }); } // 设置定时检查 setInterval(checkGitHubAccessibility, config.checkInterval); } catch (e) { console.error('初始化时出错:', e); } } // 启动脚本 if (isErrorPage) { // 错误页面情况,立即重定向 redirectToMirror(); } else { // 普通页面情况,执行完整初始化 setTimeout(init, 10); } })();