GitHub镜像加速
// ==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 = `
<div class="panel-header">
<h2>GitHub镜像加速设置</h2>
<button id="close-panel">×</button>
</div>
<div class="panel-body">
<div class="setting-group">
<label class="switch-label">
<span>启用镜像加速</span>
<label class="switch">
<input type="checkbox" id="enable-toggle" ${config.enabled ? 'checked' : ''}>
<span class="slider round"></span>
</label>
</label>
</div>
<div class="setting-group">
<label class="switch-label">
<span>自动检测模式</span>
<label class="switch">
<input type="checkbox" id="auto-mode-toggle" ${config.autoMode ? 'checked' : ''}>
<span class="slider round"></span>
</label>
</label>
<p class="setting-description">开启后,只在GitHub不可访问时自动切换到镜像站点</p>
</div>
<div class="setting-group">
<label>选择镜像站点:</label>
<select id="mirror-select">
${config.mirrors.map(mirror => `
<option value="${mirror.url}" ${config.selectedMirror === mirror.url ? 'selected' : ''}>
${mirror.name} (${mirror.url})
</option>
`).join('')}
</select>
</div>
<div class="setting-group">
<label>文件下载加速代理:</label>
<select id="file-proxy-select">
${config.fileProxies.map(proxy => `
<option value="${proxy.url}" ${config.selectedFileProxy === proxy.url ? 'selected' : ''}>
${proxy.name} (${proxy.url})
</option>
`).join('')}
</select>
</div>
<div class="setting-group">
<label>检测间隔 (分钟):</label>
<input type="number" id="check-interval" min="1" max="60"
value="${Math.round(config.checkInterval / (60 * 1000))}">
</div>
<div class="network-status">
<div class="status-indicator ${config.isGitHubAccessible ? 'good' : 'bad'}"></div>
<span>GitHub 当前状态: ${config.isGitHubAccessible ? '可访问' : '不可访问'}</span>
<button id="check-now-btn">立即检测</button>
</div>
</div>
<div class="panel-footer">
<button id="save-settings">保存设置</button>
<button id="reset-settings">重置默认</button>
</div>
`;
// 添加到页面
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);
}
})();