// ==UserScript==
// @name Github Enhancement - High Speed Download (优化版)
// @name:zh-CN Github 增强 - 高速下载 (优化版)
// @name:zh-TW Github 增強 - 高速下載 (優化版)
// @name:ru Улучшение GitHub – быстрое скачивание (оптимизированная версия)
// @version 2.6.42
// @author 辻弌 (based on X.I.U)
// @description [Optimized] Based on the original Greasy Fork script (author: X.I.U) with improvements in stability and performance. High-speed download of Git Clone/SSH, Release, Raw, Code(ZIP) and other files (Based on public welfare), project list file quick download (☁)
// @description:zh-CN [优化版] 基于 Greasy Fork 原脚本(作者:X.I.U)进行优化,提供更稳定的加速节点与性能改进。高速下载 Git Clone/SSH、Release、Raw、Code(ZIP) 等文件 (公益加速)、项目列表单文件快捷下载 (☁)
// @description:zh-TW [優化版] 基於 Greasy Fork 原腳本(作者:X.I.U)進行優化,提供更穩定的加速節點與性能改進。高速下載 Git Clone/SSH、Release、Raw、Code(ZIP) 等文件 (公益加速)、項目列表單文件快捷下載 (☁)
// @description:ru [Оптимизированная версия] На основе оригинального скрипта с Greasy Fork (автор: X.I.U) с улучшениями стабильности и производительности. Высокоскоростная загрузка Git Clone/SSH, выпусков, изначальных файлов, кода (ZIP) и других файлов (на основе общественного благосостояния), быстрая загрузка файлов из списка проектов (☁)
// @match *://github.com/*
// @match *://bgithub.xyz/*
// @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAACEUExURUxpcRgWFhsYGBgWFhcWFh8WFhoYGBgWFiUlJRcVFRkWFhgVFRgWFhgVFRsWFhgWFigeHhkWFv////////////r6+h4eHv///xcVFfLx8SMhIUNCQpSTk/r6+jY0NCknJ97e3ru7u+fn51BOTsPCwqGgoISDg6empmpoaK2srNDQ0FhXV3eXcCcAAAAXdFJOUwCBIZXMGP70BuRH2Ze/LpIMUunHkpQR34sfygAAAVpJREFUOMt1U+magjAMDAVb5BDU3W25b9T1/d9vaYpQKDs/rF9nSNJkArDA9ezQZ8wPbc8FE6eAiQUsOO1o19JolFibKCdHGHC0IJezOMD5snx/yE+KOYYr42fPSufSZyazqDoseTPw4lGJNOu6LBXVUPBG3lqYAOv/5ZwnNUfUifzBt8gkgfgINmjxOpgqUA147QWNaocLniqq3QsSVbQHNp45N/BAwoYQz9oUJEiE4GMGfoBSMj5gjeWRIMMqleD/CAzUHFqTLyjOA5zjNnwa4UCEZ2YK3khEcBXHjVBtEFeIZ6+NxYbPqWp1DLKV42t6Ujn2ydyiPi9nX0TTNAkVVZ/gozsl6FbrktkwaVvL2TRK0C8Ca7Hck7f5OBT6FFbLATkL2ugV0tm0RLM9fedDvhWstl8Wp9AFDjFX7yOY/lJrv8AkYuz7fuP8dv9izCYH+x3/LBnj9fYPBTpJDNzX+7cAAAAASUVORK5CYII=
// @grant GM_registerMenuCommand
// @grant GM_unregisterMenuCommand
// @grant GM_openInTab
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_notification
// @grant GM_setClipboard
// @grant window.onurlchange
// @sandbox JavaScript
// @license GPL-3.0 License
// @run-at document-end
// @namespace https://greasyfork.org/scripts/412245
// @supportURL https://github.com/XIU2/UserScript
// @homepageURL https://github.com/XIU2/UserScript
// ==/UserScript==
(function() {
'use strict';
// ============================================================
// 1. 配置:加速源列表(已清理无效节点)
// ============================================================
const SOURCES = {
// Release / Code(ZIP) 加速源(美国为主)
download_us: [
['https://gh.h233.eu.org/https://github.com', '美国', '[美国 Cloudflare CDN] - 由 @X.I.U/XIU2 提供'],
['https://gh.ddlc.top/https://github.com', '美国', '[美国 Cloudflare CDN] - 由 @mtr-static-official 提供'],
['https://gh-proxy.org/https://github.com', '美国', '[美国 Cloudflare CDN] - 由 gh-proxy.com 提供'],
['https://cors.isteed.cc/github.com', '美国', '[美国 Cloudflare CDN] - 由 @Lufs\'s 提供'],
['https://ghproxy.it/https://github.com', '美国', '[美国 洛杉矶] - 由 @yionchilau 提供'],
['https://github.boki.moe/https://github.com', '美国', '[美国 Cloudflare CDN] - 由 blog.boki.moe 提供'],
['https://gh.jasonzeng.dev/https://github.com', '美国', '[美国 Cloudflare CDN] - 由 gh.jasonzeng.dev 提供'],
['https://gh.monlor.com/https://github.com', '美国', '[美国 Cloudflare CDN] - 由 gh.monlor.com 提供'],
['https://github.geekery.cn/https://github.com', '美国', '[美国 Cloudflare CDN] - 由 github.geekery.cn 提供'],
['https://github.ednovas.xyz/https://github.com', '美国', '[美国 Cloudflare CDN] - 由 github.ednovas.xyz 提供'],
['https://ghfile.geekertao.top/https://github.com', '美国', '[美国 Cloudflare CDN] - 由 ghfile.geekertao.top 提供'],
['https://ghp.keleyaa.com/https://github.com', '美国', '[美国 Cloudflare CDN] - 由 ghp.keleyaa.com 提供'],
['https://gh.chjina.com/https://github.com', '美国', '[美国 Cloudflare CDN] - 由 gh.chjina.com 提供'],
['https://ghpxy.hwinzniej.top/https://github.com', '美国', '[美国 Cloudflare CDN] - 由 ghpxy.hwinzniej.top 提供'],
['https://cdn.crashmc.com/https://github.com', '美国', '[美国 Cloudflare CDN] - 由 cdn.crashmc.com 提供'],
['https://git.yylx.win/https://github.com', '美国', '[美国 Cloudflare CDN] - 由 git.yylx.win 提供'],
['https://gitproxy.mrhjx.cn/https://github.com', '美国', '[美国 Cloudflare CDN] - 由 gitproxy.mrhjx.cn 提供'],
['https://ghproxy.cxkpro.top/https://github.com', '美国', '[美国 Cloudflare CDN] - 由 ghproxy.cxkpro.top 提供'],
['https://gh.xxooo.cf/https://github.com', '美国', '[美国 Cloudflare CDN] - 由 gh.xxooo.cf 提供'],
['https://gh.idayer.com/https://github.com', '美国', '[美国 Cloudflare CDN] - 由 gh.idayer.com 提供'],
['https://down.npee.cn/?https://github.com', '美国', '[美国 Cloudflare CDN] - 由 npee社区 提供'],
['https://raw.ihtw.moe/github.com', '美国', '[美国 Cloudflare CDN] - 由 raw.ihtw.moe 提供'],
['https://xget.xi-xu.me/gh', '美国', '[美国 Cloudflare CDN] - 由 github.com/xixu-me/Xget 提供'],
['https://gh.zwy.one/https://github.com', '美国', '[美国 洛杉矶] - 由 gh.zwy.one 提供'],
['https://ghproxy.monkeyray.net/https://github.com', '美国', '[美国 洛杉矶] - 由 ghproxy.monkeyray.net 提供'],
],
// 非美国节点(也加入随机池,但流量较大,因此混入少量)
download_others: [
['https://ghproxy.net/https://github.com', '英国', '[英国伦敦] - 由 ghproxy.net 提供'],
['https://ghfast.top/https://github.com', '多国', '[日本/韩国/新加坡/美国/德国] - 由 ghproxy.link 提供'],
['https://wget.la/https://github.com', '多国', '[香港/台湾/日本/美国] - 由 ucdn.me 提供'],
],
// Git Clone 加速源
clone: [
['https://gitclone.com', '国内', '[中国 国内] - 由 GitClone 提供,有缓存,首次较慢'],
['https://wget.la/https://github.com', '香港', '[香港/台湾/日本/美国] - 由 ucdn.me 提供'],
['https://hk.gh-proxy.org/https://github.com', '香港', '[中国香港] - 由 gh-proxy.com 提供'],
['https://ghfast.top/https://github.com', '韩国', '[日本/韩国/新加坡/美国/德国] - 由 ghproxy.link 提供'],
['https://githubfast.com', '韩国', '[韩国] - 由 Github Fast 提供'],
],
// Git Clone SSH 加速源(目前仅官方443端口)
clone_ssh: [
['ssh://git@ssh.github.com:443/', 'Github 原生', '[日本/新加坡] - 官方443端口SSH,适用于限制22端口的网络'],
],
// Raw 文件加速源
raw: [
['https://raw.githubusercontent.com', 'Github 原生', '[日本 东京] 无缓存(或很短)'],
['https://wget.la/https://raw.githubusercontent.com', '香港', '[香港/台湾/日本/美国] - 由 ucdn.me 提供,无缓存'],
['https://hk.gh-proxy.org/https://raw.githubusercontent.com', '香港', '[中国香港] - 由 gh-proxy.com 提供,缓存约2小时'],
['https://ghfast.top/https://raw.githubusercontent.com', '韩国', '[日本/韩国/新加坡/美国/德国] - 由 ghproxy.link 提供,无缓存'],
['https://gh.catmak.name/https://raw.githubusercontent.com', '韩国', '[韩国 首尔] - 由 gh.catmak.name 提供'],
['https://fastly.jsdelivr.net/gh', '日本', '[日本 东京] - JSDelivr CDN,缓存,不支持>50MB及版本号分支'],
['https://cdn.gh-proxy.org/https://raw.githubusercontent.com', '日本', '[Fastly CDN] - 由 gh-proxy.com 提供,有缓存'],
['https://g.blfrp.cn/https://raw.githubusercontent.com', '日本', '[日本 东京] - 由 g.blfrp.cn 提供'],
]
};
// ============================================================
// 2. 工具函数
// ============================================================
function shuffleArray(arr, keep = 5) {
// 随机打乱并取前 keep 个
const shuffled = arr.slice();
for (let i = shuffled.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
}
return shuffled.slice(0, keep);
}
function isValidUrl(string) {
try {
const url = new URL(string);
return url.protocol === 'http:' || url.protocol === 'https:';
} catch (_) {
return false;
}
}
function safeGMGet(key, def) {
try { return GM_getValue(key, def); } catch (_) { return def; }
}
function safeGMSet(key, val) {
try { GM_setValue(key, val); } catch (_) {}
}
function safeGMNotify(opt) {
try { GM_notification(opt); } catch (_) {}
}
function safeGMClip(text) {
try { GM_setClipboard(text); } catch (_) {}
}
// 生成加速按钮样式
function getButtonStyle(region) {
const colors = {
'美国': '#3fb950',
'香港': '#58a6ff',
'韩国': '#f0883e',
'日本': '#f2cc60',
'国内': '#f97583',
'英国': '#bc8cff',
'多国': '#79c0ff',
'自定义': '#ff7b72',
'Github 原生': '#8b949e'
};
const bg = colors[region] || '#8b949e';
return `padding:0 6px; margin-right:-1px; border-radius:2px; background-color:${bg}22; border-color:var(--borderColor-default); font-size:11px; color:var(--XIU2-font-color, #888);`;
}
// ============================================================
// 3. 状态管理
// ============================================================
const STATE = {
rawFastIndex: safeGMGet('xiu2_menu_raw_fast', 1),
rawDownLink: safeGMGet('menu_rawDownLink', true),
gitClone: safeGMGet('menu_gitClone', true),
customRaw: safeGMGet('custom_raw_url', ''),
customClone: safeGMGet('custom_clone_url', ''),
customDownload: safeGMGet('custom_download_url', ''),
};
// 如果自定义加速源有效,插入到对应列表头部
function buildDownloadUrls() {
let us = SOURCES.download_us.slice();
let others = SOURCES.download_others.slice();
// 自定义优先
if (STATE.customDownload && isValidUrl(STATE.customDownload)) {
us.unshift([STATE.customDownload, '自定义', '[自定义 Release/Code(ZIP) 加速源]']);
}
// 随机取5个美国 + 全部其他(或随机取部分),这里简单合并全部其他,但为了均衡,随机取2个其他
const shuffledOthers = shuffleArray(others, 2);
const shuffledUs = shuffleArray(us, 5);
return shuffledUs.concat(shuffledOthers);
}
function getRawUrls() {
let raw = SOURCES.raw.slice();
if (STATE.customRaw && isValidUrl(STATE.customRaw)) {
raw.splice(1, 0, [STATE.customRaw, '自定义', '[自定义 Raw 加速源]']);
}
return raw;
}
function getCloneUrls() {
let clone = SOURCES.clone.slice();
if (STATE.customClone && isValidUrl(STATE.customClone)) {
clone.unshift([STATE.customClone, '自定义', '[自定义 Git Clone 加速源]']);
}
return clone;
}
// ============================================================
// 4. 核心功能
// ============================================================
let styleElement = null;
function updateColorMode() {
if (!styleElement) {
styleElement = document.createElement('style');
styleElement.id = 'XIU2-Github';
document.documentElement.appendChild(styleElement);
}
let backColor = '#ffffff', fontColor = '#888888';
const mode = document.documentElement.dataset.colorMode;
const darkTheme = document.documentElement.dataset.darkTheme;
if (mode === 'dark') {
if (darkTheme === 'dark_dimmed') {
backColor = '#272e37'; fontColor = '#768390';
} else {
backColor = '#161a21'; fontColor = '#97a0aa';
}
} else if (mode === 'auto') {
if (window.matchMedia('(prefers-color-scheme: dark)').matches || (document.documentElement.dataset.lightTheme && document.documentElement.dataset.lightTheme.includes('dark'))) {
if (darkTheme === 'dark_dimmed') {
backColor = '#272e37'; fontColor = '#768390';
} else if (!darkTheme || !darkTheme.includes('light')) {
backColor = '#161a21'; fontColor = '#97a0aa';
}
}
}
styleElement.textContent = `.XIU2-RS a {--XIU2-background-color: ${backColor}; --XIU2-font-color: ${fontColor};}`;
}
// ---------- Release ----------
function addRelease() {
const boxes = document.querySelectorAll('.Box-footer');
if (!boxes.length || !location.pathname.includes('/releases')) return;
const urls = buildDownloadUrls();
const divDisplay = document.documentElement.clientWidth > 755 ? 'margin-top:-3px;margin-left:8px;display:inherit;' : 'margin-left:-90px;';
// 添加样式适应小屏幕
const styleTag = document.createElement('style');
styleTag.textContent = '@media (min-width:768px){.Box-footer li.Box-row>div>span.color-fg-muted{min-width:27px!important;}}';
boxes[0].appendChild(styleTag);
boxes.forEach(box => {
if (box.querySelector('.XIU2-RS')) return;
box.querySelectorAll('li.Box-row a').forEach(link => {
const href = link.href.split(location.host)[1];
if (!href) return;
let html = `
`;
urls.forEach(([url, region, title]) => {
const fullUrl = url + href;
const style = getButtonStyle(region);
html += `
${region}`;
});
html += '
';
link.parentElement.nextElementSibling?.insertAdjacentHTML('beforeend', html);
});
});
}
// ---------- Download ZIP ----------
function addDownloadZIP(target) {
const actionList = target.querySelector('ul[class^=prc-ActionList-ActionList-]');
if (!actionList) return;
const zipItem = actionList.querySelector('li:last-child');
if (!zipItem) return;
const zipLink = zipItem.querySelector('a[href^="/"][href$=".zip"]');
if (!zipLink) return;
const href = zipLink.getAttribute('href');
if (!href) return;
const urls = buildDownloadUrls();
let html = '';
urls.forEach(([url, region, title]) => {
const fullUrl = url + href;
const clone = zipItem.cloneNode(true);
const a = clone.querySelector('a[href$=".zip"]');
const span = clone.querySelector('span[id]');
if (a) {
a.href = fullUrl;
a.setAttribute('target', '_blank');
a.setAttribute('rel', 'noreferrer noopener nofollow');
a.setAttribute('title', title + '\n\n提示:鼠标中键或Ctrl+点击在后台打开');
}
if (span) {
span.textContent = 'Download ZIP ' + region;
}
html += clone.outerHTML;
});
zipItem.insertAdjacentHTML('afterend', html);
}
// ---------- Git Clone ----------
function addGitClone(target) {
const input = target.querySelector('input[value^="https:"]:not([title])');
if (!input) return;
const hrefSplit = input.value.split(location.host)[1];
if (!hrefSplit) return;
const cloneUrls = getCloneUrls();
// 隐藏原生复制按钮
if (input.nextElementSibling) input.nextElementSibling.hidden = true;
if (input.parentElement.nextElementSibling?.tagName === 'P') {
input.parentElement.nextElementSibling.textContent += ' (↑点击自动复制)';
}
const prefix = STATE.gitClone ? 'git clone ' : '';
// 修改原输入框
input.value = prefix + input.value;
input.setAttribute('value', input.value);
let html = '';
const parentClass = input.parentElement.className;
cloneUrls.forEach(([url, region, title]) => {
const fullUrl = (url === 'https://gitclone.com') ? url + '/github.com' + hrefSplit : url + hrefSplit;
const clone = input.cloneNode(true);
clone.title = `${fullUrl}\n\n${title.replace(/
/g, '\n')}\n\n点击复制`;
clone.setAttribute('value', prefix + fullUrl);
html += `${clone.outerHTML}
`;
});
input.parentElement.insertAdjacentHTML('afterend', html);
// 点击复制事件(委托)
const wrapper = input.parentElement.parentElement;
if (!wrapper.classList.contains('XIU2-GCP')) {
wrapper.classList.add('XIU2-GCP');
wrapper.addEventListener('click', (e) => {
if (e.target.tagName === 'INPUT') {
safeGMClip(e.target.value);
}
});
}
}
function addGitCloneSSH(target) {
const input = target.querySelector('input[value^="git@"]:not([title])');
if (!input) return;
const hrefSplit = input.value.split(':')[1];
if (!hrefSplit) return;
const cloneUrls = SOURCES.clone_ssh; // 目前只有官方
if (input.nextElementSibling) input.nextElementSibling.hidden = true;
if (input.parentElement.nextElementSibling?.tagName === 'P') {
input.parentElement.nextElementSibling.textContent += ' (↑点击自动复制)';
}
const prefix = STATE.gitClone ? 'git clone ' : '';
input.value = prefix + input.value;
input.setAttribute('value', input.value);
let html = '';
const parentClass = input.parentElement.className;
cloneUrls.forEach(([url, region, title]) => {
const fullUrl = url + hrefSplit;
const clone = input.cloneNode(true);
clone.title = `${fullUrl}\n\n${title.replace(/
/g, '\n')}\n\n点击复制`;
clone.setAttribute('value', prefix + fullUrl);
html += `${clone.outerHTML}
`;
});
input.parentElement.insertAdjacentHTML('afterend', html);
const wrapper = input.parentElement.parentElement;
if (!wrapper.classList.contains('XIU2-GCP')) {
wrapper.classList.add('XIU2-GCP');
wrapper.addEventListener('click', (e) => {
if (e.target.tagName === 'INPUT') {
safeGMClip(e.target.value);
}
});
}
}
// ---------- Raw ----------
function addRawFile() {
const rawBtn = document.querySelector('a[data-testid="raw-button"]');
if (!rawBtn) return;
const href = location.href.replace(`https://${location.host}`, '');
const href2 = href.replace('/blob/', '/');
const rawUrls = getRawUrls();
let html = '';
rawUrls.slice(1).forEach(([url, region, title]) => {
let fullUrl;
if (url.includes('/gh') && url.endsWith('/gh') && !url.includes('cdn.staticaly.com')) {
fullUrl = url + href.replace('/blob/', '@');
} else {
fullUrl = url + href2;
}
html += `${region.replace(/ \d/,'')}`;
});
document.querySelectorAll('.XIU2-RF').forEach(el => el.remove());
rawBtn.insertAdjacentHTML('afterend', html);
}
// ---------- Raw 单文件快捷下载(☁) ----------
function addRawDownLink() {
if (!STATE.rawDownLink) return;
if (location.pathname.includes('/tags')) return;
const files = document.querySelectorAll('div.Box-row svg.octicon.octicon-file, .react-directory-filename-column>svg.color-fg-muted');
if (!files.length) return;
if (document.querySelector('a.fileDownLink')) return; // 已存在
const rawUrls = getRawUrls();
const selected = rawUrls[STATE.rawFastIndex] || rawUrls[0];
const [baseUrl, region, title] = selected;
files.forEach(fileSvg => {
const tr = fileSvg.closest('div.Box-row, tr') || fileSvg.parentElement.parentElement;
if (!tr) return;
const link = tr.querySelector('[role="rowheader"] > .css-truncate > a, .react-directory-truncate>a');
if (!link) return;
const name = link.textContent.trim();
const href = link.getAttribute('href');
if (!href) return;
const href2 = href.replace('/blob/', '/');
let fullUrl;
if (baseUrl.includes('/gh') && baseUrl.endsWith('/gh') && !baseUrl.includes('cdn.staticaly.com')) {
fullUrl = baseUrl + href.replace('/blob/', '@');
} else {
fullUrl = baseUrl + href2;
}
const svgIcon = ``;
fileSvg.insertAdjacentHTML('afterend', `${svgIcon}`);
// 鼠标悬停事件
tr.addEventListener('mouseenter', function(e) {
const links = this.querySelectorAll('.fileDownLink');
const svgs = this.querySelectorAll('svg.octicon.octicon-file, svg.color-fg-muted');
links.forEach(el => el.style.display = 'inline');
svgs.forEach(el => el.style.display = 'none');
});
tr.addEventListener('mouseleave', function(e) {
const links = this.querySelectorAll('.fileDownLink');
const svgs = this.querySelectorAll('svg.octicon.octicon-file, svg.color-fg-muted');
links.forEach(el => el.style.display = 'none');
svgs.forEach(el => el.style.display = 'inline');
});
});
}
// ============================================================
// 5. 菜单管理
// ============================================================
let menuIds = { raw: null, downLink: null, gitClone: null, custom: null, feedback: null };
function registerMenu() {
// 清理旧菜单
Object.values(menuIds).forEach(id => { if (id) try { GM_unregisterMenuCommand(id); } catch (_) {} });
const rawUrls = getRawUrls();
const idx = STATE.rawFastIndex >= rawUrls.length ? 0 : STATE.rawFastIndex;
const region = rawUrls[idx]?.[1] || '未知';
const emojis = ['0️⃣','1️⃣','2️⃣','3️⃣','4️⃣','5️⃣','6️⃣','7️⃣','8️⃣','9️⃣','🔟'];
const emoji = emojis[idx] || '🔟';
menuIds.raw = GM_registerMenuCommand(`${emoji} [ ${region} ] 加速源 (☁) - 点击切换`, () => {
const newIdx = (STATE.rawFastIndex + 1) % rawUrls.length;
STATE.rawFastIndex = newIdx;
safeGMSet('xiu2_menu_raw_fast', newIdx);
// 删除旧☁链接并重新添加
document.querySelectorAll('.fileDownLink').forEach(el => el.remove());
addRawDownLink();
safeGMNotify({ text: `已切换加速源为:${rawUrls[newIdx][1]}`, timeout: 3000 });
registerMenu();
});
menuIds.downLink = GM_registerMenuCommand(`${STATE.rawDownLink ? '✅' : '❌'} 项目列表单文件快捷下载 (☁)`, () => {
STATE.rawDownLink = !STATE.rawDownLink;
safeGMSet('menu_rawDownLink', STATE.rawDownLink);
safeGMNotify({ text: `已${STATE.rawDownLink?'开启':'关闭'} [项目列表单文件快捷下载] 功能\n(点击刷新网页生效)`, timeout: 3500, onclick: () => location.reload() });
registerMenu();
});
menuIds.gitClone = GM_registerMenuCommand(`${STATE.gitClone ? '✅' : '❌'} 添加 git clone 命令`, () => {
STATE.gitClone = !STATE.gitClone;
safeGMSet('menu_gitClone', STATE.gitClone);
safeGMNotify({ text: `已${STATE.gitClone?'开启':'关闭'} [添加 git clone 命令] 功能\n(点击刷新网页生效)`, timeout: 3500, onclick: () => location.reload() });
registerMenu();
});
menuIds.custom = GM_registerMenuCommand(`#️⃣ 自定义加速源`, () => {
const keys = [
{ key: 'custom_raw_url', desc: 'Raw 加速源', placeholder: 'https://example.com/https://raw.githubusercontent.com' },
{ key: 'custom_clone_url', desc: 'Git Clone 加速源', placeholder: 'https://example.com/https://github.com' },
{ key: 'custom_download_url', desc: 'Release/Code(ZIP) 加速源', placeholder: 'https://example.com/https://github.com' }
];
(function promptNext(i) {
if (i >= keys.length) {
safeGMNotify({ text: '自定义加速源设置完成!\n(刷新网页生效)', timeout: 3500, onclick: () => location.reload() });
return;
}
const { key, desc, placeholder } = keys[i];
const current = safeGMGet(key, '');
const input = prompt(`请输入自定义${desc}地址:\n当前:${current || '(未设置)'}\n\n示例:${placeholder}\n\n留空为不设置\n确定保存并继续,取消终止`, current);
if (input === null) return;
const val = input.trim();
if (val && !isValidUrl(val)) {
safeGMNotify({ text: `❌ 地址无效,请重新输入`, timeout: 3000 });
promptNext(i);
return;
}
safeGMSet(key, val);
// 更新状态
STATE.customRaw = safeGMGet('custom_raw_url', '');
STATE.customClone = safeGMGet('custom_clone_url', '');
STATE.customDownload = safeGMGet('custom_download_url', '');
promptNext(i + 1);
})(0);
});
menuIds.feedback = GM_registerMenuCommand('💬 反馈 & 建议', () => {
GM_openInTab('https://github.com/XIU2/UserScript', { active: true });
GM_openInTab('https://greasyfork.org/zh-CN/scripts/412245/feedback', { active: true });
});
}
// ============================================================
// 6. 观察者与初始化
// ============================================================
function onUrlChange() {
updateColorMode();
if (location.pathname.includes('/releases')) addRelease();
setTimeout(addRawFile, 800);
setTimeout(addRawDownLink, 1200);
// 重新绑定鼠标事件(若已存在)
setTimeout(() => {
document.querySelectorAll('.fileDownLink').forEach(el => {
const tr = el.closest('div.Box-row, tr');
if (tr && !tr._bound) {
tr._bound = true;
tr.addEventListener('mouseenter', function(e) {
this.querySelectorAll('.fileDownLink').forEach(l => l.style.display = 'inline');
this.querySelectorAll('svg.octicon.octicon-file, svg.color-fg-muted').forEach(s => s.style.display = 'none');
});
tr.addEventListener('mouseleave', function(e) {
this.querySelectorAll('.fileDownLink').forEach(l => l.style.display = 'none');
this.querySelectorAll('svg.octicon.octicon-file, svg.color-fg-muted').forEach(s => s.style.display = 'inline');
});
}
});
}, 1500);
}
// 监听 URL 变化 (pjax)
if (window.onurlchange === undefined) {
const origPush = history.pushState;
history.pushState = function() {
const ret = origPush.apply(this, arguments);
window.dispatchEvent(new Event('urlchange'));
return ret;
};
const origReplace = history.replaceState;
history.replaceState = function() {
const ret = origReplace.apply(this, arguments);
window.dispatchEvent(new Event('urlchange'));
return ret;
};
window.addEventListener('popstate', () => window.dispatchEvent(new Event('urlchange')));
}
window.addEventListener('urlchange', onUrlChange);
// MutationObserver 监听动态内容 (Release, Clone, ZIP)
const observer = new MutationObserver((mutations) => {
if (location.pathname.includes('/releases')) {
for (const mut of mutations) {
for (const node of mut.addedNodes) {
if (node.nodeType === 1 && node.matches && node.matches('.Box-footer')) {
addRelease();
break;
}
}
}
} else if (document.querySelector('#repo-title-component')) {
// 项目首页,监听到弹出层变化时处理 Clone 和 ZIP
for (const mut of mutations) {
for (const node of mut.addedNodes) {
if (node.nodeType === 1 && node.id === '__primerPortalRoot__') {
addGitClone(node);
addGitCloneSSH(node);
addDownloadZIP(node);
}
}
}
}
});
// 缩小观察范围,只观察 #repo-content-pjax-container 或 body
const targetNode = document.querySelector('#repo-content-pjax-container') || document.body;
observer.observe(targetNode, { childList: true, subtree: true });
// 初始执行
setTimeout(() => {
updateColorMode();
registerMenu();
onUrlChange();
// 额外处理可能已经存在的 Release / Clone
addRelease();
// 如果当前是项目首页,尝试添加 Clone/ZIP(但DOM可能已存在)
if (document.querySelector('#repo-title-component')) {
const portal = document.querySelector('#__primerPortalRoot__');
if (portal) {
addGitClone(portal);
addGitCloneSSH(portal);
addDownloadZIP(portal);
}
}
}, 500);
})();