// ==UserScript== // @name Github Enhancement - High Speed Download (优化版) // @name:zh-CN Github 增强 - 高速下载 (优化版) // @name:zh-TW Github 增強 - 高速下載 (優化版) // @name:ru Улучшение GitHub – быстрое скачивание (оптимизированная версия) // @version 2.6.54 // @author 辻弌 (based on X.I.U) // @description Optimized based on the original Greasy Fork script by X.I.U, with improved 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 Оптимизировано на основе оригинального скрипта X.I.U с Greasy Fork, улучшена стабильность и производительность. Высокоскоростная загрузка 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. 配置:加速源列表(已清理无效节点) // 注意:每个数组保留第四项作为 archive 链接的替换前缀(若无则空) // ============================================================ const SOURCES = { 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 提供', ''], ], 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 提供'], ], clone_ssh: [ ['ssh://git@ssh.github.com:443/', 'Github 原生', '[日本/新加坡] - 官方443端口SSH,适用于限制22端口的网络'], ], 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) { 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 (_) {} } // ============================================================ // 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) 加速源]', '']); } // 随机取4个美国节点 const shuffledUs = shuffleArray(us, 4); // 取全部其他节点(最多3个) const shuffledOthers = shuffleArray(others, others.length); 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};}`; return { backColor, fontColor }; } // ---------- Release(双重策略:原版优先 + 通用回退) ---------- function addRelease() { if (!location.pathname.includes('/releases')) return; const { backColor, fontColor } = updateColorMode(); const urls = buildDownloadUrls(); const divDisplay = document.documentElement.clientWidth > 755 ? 'margin-top:-3px;margin-left:8px;display:inherit;' : 'margin-left:-90px;'; // ---- 策略1:原版 .Box-footer 逻辑 ---- const boxes = document.querySelectorAll('.Box-footer'); if (boxes.length) { // 添加样式(仅第一次) if (!document.querySelector('#XIU2-Release-Style')) { const styleTag = document.createElement('style'); styleTag.id = 'XIU2-Release-Style'; styleTag.textContent = '@media (min-width:768px){.Box-footer li.Box-row>div>span.color-fg-muted{min-width:27px!important;}}'; document.head.appendChild(styleTag); } let hasProcessed = false; boxes.forEach(box => { if (box.querySelector('.XIU2-RS')) return; const links = box.querySelectorAll('li.Box-row a'); if (!links.length) return; hasProcessed = true; links.forEach(link => { const href = link.href.split(location.host)[1]; if (!href) return; let html = `
`; urls.forEach(([url, region, title, replacePrefix]) => { let fullUrl; // 如果存在替换前缀且当前链接是 archive,则使用替换前缀 if (replacePrefix && href.includes('/archive/')) { fullUrl = replacePrefix + href; } else { fullUrl = url + href; } html += `${region}`; }); html += '
'; const nextSibling = link.parentElement.nextElementSibling; if (nextSibling) { nextSibling.insertAdjacentHTML('beforeend', html); } else { link.parentElement.insertAdjacentHTML('beforeend', html); } }); }); if (hasProcessed) return; // 原版成功处理,直接返回 } // ---- 策略2:通用选择器(回退) ---- const allLinks = document.querySelectorAll('a[href*="/releases/download/"], a[href*="/archive/"]'); if (!allLinks.length) return; const processedParents = new Set(); const divStyle = 'margin-left: 6px; display: inline-block;'; allLinks.forEach(link => { const parent = link.closest('li, .Box-row, [role="row"], .release-assets-item, tr'); if (!parent) return; if (processedParents.has(parent)) return; if (parent.querySelector('.XIU2-RS')) return; processedParents.add(parent); const href = link.href.split(location.host)[1]; if (!href) return; let html = `
`; urls.forEach(([url, region, title, replacePrefix]) => { let fullUrl; if (replacePrefix && href.includes('/archive/')) { fullUrl = replacePrefix + href; } else { fullUrl = url + href; } html += `${region}`; }); html += '
'; link.parentElement.insertAdjacentHTML('afterend', 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', ``); 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. 观察者与初始化 // ============================================================ let releaseListenerAdded = false; function ensureReleaseListener() { if (!releaseListenerAdded && location.pathname.includes('/releases')) { document.addEventListener('click', function(e) { const toggle = e.target.closest('[aria-expanded]') || e.target.closest('.js-release-assets-toggle') || e.target.closest('[data-target="release-assets-toggle"]') || e.target.closest('button[aria-label*="Show"]') || e.target.closest('button[aria-label*="Hide"]'); if (toggle) { setTimeout(addRelease, 600); setTimeout(addRelease, 1200); } }); releaseListenerAdded = true; } } const releaseObserver = new MutationObserver((mutations) => { if (!location.pathname.includes('/releases')) return; let hasNewLinks = false; for (const mut of mutations) { for (const node of mut.addedNodes) { if (node.nodeType === 1) { if (node.querySelector && node.querySelector('a[href*="/releases/download/"]')) { hasNewLinks = true; break; } } } if (hasNewLinks) break; } if (hasNewLinks) { setTimeout(addRelease, 300); } }); setTimeout(() => { releaseObserver.observe(document.body, { childList: true, subtree: true }); }, 1000); function onUrlChange() { updateColorMode(); if (location.pathname.includes('/releases')) { setTimeout(addRelease, 300); ensureReleaseListener(); } 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); } 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); 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')) { setTimeout(addRelease, 300); break; } } } } else if (document.querySelector('#repo-title-component')) { for (const mut of mutations) { for (const node of mut.addedNodes) { if (node.nodeType === 1 && node.id === '__primerPortalRoot__') { addGitClone(node); addGitCloneSSH(node); addDownloadZIP(node); } } } } }); const targetNode = document.querySelector('#repo-content-pjax-container') || document.body; observer.observe(targetNode, { childList: true, subtree: true }); setTimeout(() => { updateColorMode(); registerMenu(); onUrlChange(); setTimeout(addRelease, 300); ensureReleaseListener(); if (document.querySelector('#repo-title-component')) { const portal = document.querySelector('#__primerPortalRoot__'); if (portal) { addGitClone(portal); addGitCloneSSH(portal); addDownloadZIP(portal); } } }, 500); })();