// ==UserScript== // @name 火箭按钮 // @namespace https://docs.scriptcat.org/ // @version 0.7.6 // @description 极其简洁的下载链接加速器按钮,对Github的文件下载链接,通过镜像加速器进行下载所需文件。(支持 releases 和 Download ZIP) // @author flowsaro.com // @match *://github.com/* // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACxUlEQVR4nO3dMW8SYRzH8duKg5Nuhdo3UNib+hoUlqeNielm1LVLt7obEyOkATqQmG6Y6FSXGh3VOBjQNlUDDQ+yOPEGzjwrPFdLwD6/g+8n+U+Xu3ue+6YJhaZEEQDgf7tX+5o1tVbXN+5Y6PUtHLN/srpZb8e+ccdCr2/hGIJoMQTRYgiixRAkjK1qe8PUWo2xqbebiUHq7abvHHet0PtJPVNrbyc9+EnHXSv0flLPEESLIYgWQxAthiBaDEG0GIJoMQTRYgiixRBEiyFIGFvV05vuTcGxOWjtzCzIQWvHdw9379D7l+Pe/Nuc0YOf/Cen1Qi9fzkEEUMQMQQRQxAxBEl5kAcvv8ftHz3vuGMEueIgjw9P4t/9vnceHRJkagQRQxAxBBFDkJQE2X11Gr/+2Bmboy/dxCDumO+c3eYpQaYN8uzoLPHBTzruWgS5JIKIIYgYgoghSIpeZb351Bmbt/94leU7x12LIJfE7yFiCCKGIGIIIoYgc/CJ4befPe/wieEM8Jm6GIKIIYgYgoghiBiCBLJUrHzIFMu/Ruf2k3fDUEHcvX1rcmuN5l2mVO5kSpV4dNb3joPEcOPu7VuTW2s07wgihiBiCCKGIAFcv/viRqZYrnqnVB6mKMgwaR9uj1FaLN3ZX/VvMHk0g1QSx+0xSguCiCGIGIKIIYgYgoghiBiCiCGIGIKIIYiYa8XnWd8ncBfN+t7xmam1zke/T+p++fOfh0/fx7MYdy3Pd1adu3tPul63x2gR2Vxh264U4plMrsD/XJyWJYgWSxAtliBaLEG0WIJosQTRYgmixRJEiyWIFksQLZYgWixBtFiCaLEE0WIJosUSRIsliJZ+Lr/RW8k3RsfmCs0LHnzTd467Vuj9zK3BrcJqUhB3LPT6Fs6AIFoGBNEyIIiWAUG09JbXsr1cvuud5bXF/CM2AIiu1F+d56Qeo24SAwAAAABJRU5ErkJggg== // @grant none // @noframes // @license All Rights Reserved // ==/UserScript== (function () { 'use strict'; const mirrorSites = [ { name: "gh-proxy.com", url: "https://gh-proxy.com" }, { name: "gh-proxy.org", url: "https://gh-proxy.org" }, { name: "ghproxy.com", url: "https://mirror.ghproxy.com" }, ]; let currentMirror = mirrorSites[0]; function mirrorUrl(m, original) { return m.url.replace(/\/+$/, "") + "/" + original; } // ---------- 样式 ---------- const style = document.createElement("style"); style.textContent = ` .gh-mirror-split { position: relative; display: inline-flex; align-items: stretch; margin-left: 8px; vertical-align: middle; flex-shrink: 0; /* 防止按钮被 li 压缩 */ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif; } .gh-mirror-split a, .gh-mirror-split button { font-size: 12px; line-height: 20px; padding: 3px 12px; border: 1px solid var(--color-border-default, #d0d7de); background-color: var(--color-btn-bg, #f6f8fa); color: var(--color-fg-default, #24292f); cursor: pointer; text-decoration: none !important; box-shadow: var(--color-btn-shadow, 0 1px 0 rgba(27,31,36,0.04)); transition: background-color 0.1s ease; box-sizing: border-box; } .gh-mirror-split a:hover, .gh-mirror-split a:active, .gh-mirror-split a:focus, .gh-mirror-split button:hover { text-decoration: none !important; background-color: var(--color-btn-hover-bg, #f3f4f6); border-color: var(--color-btn-hover-border, #d0d7de); } .gh-mirror-split .split-main { border-radius: 6px 0 0 6px; border-right: none; } .gh-mirror-split .split-arrow { border-radius: 0 6px 6px 0; padding-left: 8px; padding-right: 8px; } .gh-mirror-menu { position: absolute; right: 0; top: calc(100% + 4px); min-width: 160px; margin: 0; padding: 4px 0; list-style: none; background-color: var(--color-canvas-overlay, #ffffff); border: 1px solid var(--color-border-default, #d0d7de); border-radius: 6px; box-shadow: var(--color-shadow-large, 0 8px 24px rgba(140,149,159,0.2)); z-index: 9999; display: none; } .gh-mirror-menu.open { display: block; } .gh-mirror-menu li { padding: 4px 12px; font-size: 12px; cursor: pointer; color: var(--color-fg-default, #24292f); white-space: nowrap; } .gh-mirror-menu li:hover { background-color: var(--color-accent-subtle, #ddf4ff); color: var(--color-accent-fg, #0969da); } .gh-mirror-menu li.selected { color: var(--color-accent-fg, #0969da); font-weight: 600; } /* 包含加速按钮的 li:a 与按钮水平平铺 */ li.gh-mirror-li { display: flex !important; align-items: center; } `; document.head.appendChild(style); // ---------- 全局工具 ---------- function closeAllMenus() { document.querySelectorAll(".gh-mirror-menu.open").forEach(m => m.classList.remove("open")); } document.addEventListener("click", function (e) { if (!e.target.closest(".gh-mirror-split")) closeAllMenus(); }); function updateMainLinks() { document.querySelectorAll(".gh-mirror-split .split-main").forEach(a => { const original = a.dataset.originalHref; if (original) { a.href = mirrorUrl(currentMirror, original); a.title = "通过 " + currentMirror.name + " 下载"; } }); } // ---------- 创建分体按钮 ---------- function addSplitToLink(link) { if (link.dataset.mirrorSplit) return; link.dataset.mirrorSplit = "1"; const wrap = document.createElement("span"); wrap.className = "gh-mirror-split"; const main = document.createElement("a"); main.className = "split-main"; main.dataset.originalHref = link.href; main.href = mirrorUrl(currentMirror, link.href); main.textContent = "下载"; main.title = "通过 " + currentMirror.name + " 下载"; main.target = "_blank"; main.rel = "noopener noreferrer"; main.dataset.mirrorSplit = "1"; const arrow = document.createElement("button"); arrow.type = "button"; arrow.className = "split-arrow"; arrow.textContent = "▾"; arrow.title = "选择镜像下载"; arrow.setAttribute("aria-label", "选择镜像下载"); const menu = document.createElement("ul"); menu.className = "gh-mirror-menu"; mirrorSites.forEach(m => { const li = document.createElement("li"); li.dataset.mirrorUrl = m.url; li.dataset.mirrorName = m.name; li.addEventListener("click", function (e) { e.stopPropagation(); window.open(mirrorUrl(m, link.href), "_blank"); currentMirror = m; updateMainLinks(); updateSelectedMarkers(); closeMenu(); }); menu.appendChild(li); }); function updateSelectedMarkers() { menu.querySelectorAll("li").forEach(li => { const selected = li.dataset.mirrorUrl === currentMirror.url; li.classList.toggle("selected", selected); li.textContent = (selected ? "✓ " : "") + li.dataset.mirrorName; }); } updateSelectedMarkers(); arrow.addEventListener("click", function (e) { e.stopPropagation(); const isOpen = menu.classList.contains("open"); closeAllMenus(); if (!isOpen) menu.classList.add("open"); }); function closeMenu() { menu.classList.remove("open"); } wrap.appendChild(main); wrap.appendChild(arrow); wrap.appendChild(menu); // ---------- 插入:与 a 同级,放入同一个 li ---------- link.parentNode.insertBefore(wrap, link.nextSibling); // Download ZIP:给所在 li 加 flex,让 a 和按钮水平平铺 if (link.href.includes("archive/refs/heads") && link.parentNode.tagName === "LI") { link.parentNode.classList.add("gh-mirror-li"); } } // ---------- 防抖处理动态加载 ---------- let debounceTimer; function processLinks() { clearTimeout(debounceTimer); debounceTimer = setTimeout(() => { observer.disconnect(); document.querySelectorAll( "a[href*='releases/download']:not([data-mirror-split]), " + "a[href*='archive/refs/heads']:not([data-mirror-split])" ).forEach(addSplitToLink); observer.observe(document.body, { childList: true, subtree: true }); }, 250); } const observer = new MutationObserver(processLinks); observer.observe(document.body, { childList: true, subtree: true }); processLinks(); })();