// ==UserScript== // @name Github Enhancement - High Speed Download (优化版) // @name:zh-CN Github 增强 - 高速下载 (优化版) // @name:zh-TW Github 增強 - 高速下載 (優化版) // @name:ru Улучшение GitHub – быстрое скачивание (оптимизированная версия) // @version 2.6.43 // @author 辻弌 (based on X.I.U) // @description 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 高速下载 Git Clone/SSH、Release、Raw、Code(ZIP) 等文件 (公益加速)、项目列表单文件快捷下载 (☁) // @description:zh-TW 高速下載 Git Clone/SSH、Release、Raw、Code(ZIP) 等文件 (公益加速)、項目列表單文件快捷下載 (☁) // @description:ru Высокоскоростная загрузка 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', ``); // 鼠标悬停事件 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); })();