- // ==UserScript==
- // @name GitHub镜像
- // @name:en GitHub Mirror
- // @description GitHub镜像,加速访问GitHub,支持Clone、Release、Raw、Zip加速。
- // @description:en GitHub mirror. Accelerate access to GitHub. Support Clone, Release, RAW and ZIP acceleration.
- // @namespace https://github.com/HaleShaw
- // @version 1.4.0
- // @author HaleShaw
- // @copyright 2021+, HaleShaw (https://github.com/HaleShaw)
- // @license AGPL-3.0-or-later
- // @homepage https://github.com/HaleShaw/TM-GitHubMirror
- // @supportURL https://github.com/HaleShaw/TM-GitHubMirror/issues
- // @contributionURL https://www.jianwudao.com/
- // @icon https://github.githubassets.com/favicon.ico
- // @require https://cdn.jsdelivr.net/npm/jquery@3/dist/jquery.min.js
- // @include *://github.com/*
- // @compatible Chrome
- // @run-at document-end
- // @grant GM_addStyle
- // @grant GM_getValue
- // @grant GM_setValue
- // ==/UserScript==
-
- // ==OpenUserJS==
- // @author HaleShaw
- // @collaborator HaleShaw
- // ==/OpenUserJS==
-
- (function () {
- ("use strict");
-
- const style = `
- /* The menu container */
- .menuContainer {
- width: 600px;
- }
-
- .menuBlock {
- padding: 4px 0;
- color: #990000;
- }
-
- .menuLeftIcon{
- margin-right:5px;
- }
-
- .menuButtonLabel{
- margin-right: 2rem;
- }
-
- .menuButtonCheck{
- vertical-align: text-bottom;
- margin: 0 3px;
- }
-
- .SelectMenu-list {
- padding: 0 16px;
- }
-
- .SelectMenu-list > a.SelectMenu-item {
- padding-left: 0;
- padding-right: 0;
- margin-top: 4px;
- }
-
- .clone {
- padding-left: 3px 12px !important;
- }
-
- .Box-body.download-box {
- border-bottom: none;
- width: 100%;
- text-align: right;
- padding: unset;
- }
-
- .Box-body.download-box > a {
- font-size: 11px;
- margin: 0 3px;
- padding: 0 6px;
- }
- `;
-
- const mirrors = [
- {
- id: 0,
- name: "CnpmJS",
- url: "https://github.com.cnpmjs.org",
- description: "cnpmjs.org",
- },
- {
- id: 1,
- name: "FastGit",
- url: "https://hub.fastgit.org",
- description: "KevinZonda",
- },
- {
- id: 2,
- name: "FastGit",
- url: "https://download.fastgit.org",
- description: "KevinZonda",
- },
- {
- id: 3,
- name: "FastGit",
- url: "https://raw.fastgit.org",
- description: "KevinZonda",
- },
- {
- id: 4,
- name: "WuYanZheShui",
- url: "https://github.wuyanzheshui.workers.dev",
- description: "WuYanZheShui. Maximum of 100,000 calls per day",
- },
- {
- id: 5,
- name: "RC1844",
- url: "https://github.rc1844.workers.dev",
- description: "RC1844. Maximum of 100,000 calls per day",
- },
- {
- id: 6,
- name: "jsDelivr",
- url: "https://cdn.jsdelivr.net/gh",
- description: "The total file size of the current branch of the project cannot exceed 50MB",
- },
- {
- id: 7,
- name: "IAPK",
- url: "https://github.iapk.cc",
- description: "IAPK",
- },
- {
- id: 8,
- name: "Ecalose",
- url: "https://gh.haval.gq",
- description: "Ecalose. Maximum of 100,000 calls per day",
- },
- {
- id: 9,
- name: "IAPK",
- url: "https://iapk.cc/github?url=https://github.com",
- description: "IAPK",
- },
- {
- id: 10,
- name: "Statically",
- url: "https://cdn.staticaly.com/gh",
- description:
- "Only images and source code files are supported, and the file size is limited to 30MB",
- },
- {
- id: 11,
- name: "Github 原生",
- url: "ssh://git@ssh.github.com:443/",
- description: "Github 官方提供的 443 端口的 SSH,适用于限制访问 22 端口的网络环境",
- },
- {
- id: 12,
- name: "FastGit",
- url: "git@ssh.fastgit.org:",
- description: "FastGit 香港",
- },
- ];
-
- //添加对应索引即可使用
- const cloneSet = [0, 1, 4];
- const sshSet = [11, 12];
- const browseSet = [0, 1, 4, 5, 7, 8];
- const downloadSet = [2, 4, 5, 8, 9];
- const rawSet = [3, 4, 5, 6, 8, 9, 10];
-
- const messages = {
- en: {
- menuButton: {
- name: "CloneMirror",
- title: "Open List",
- header: "Quickly clone and Mirror sites",
- block:
- "Please do not login in the mirror site. I will not be responsible for any loss caused by this.",
- },
- },
- zh: {
- menuButton: {
- name: "克隆与镜像",
- title: "打开列表",
- header: "快速克隆与镜像站点",
- block: "请不要在镜像网站登录(不可用)账号,若因此造成任何损失本人概不负责",
- },
- },
- };
-
- const icons = {
- closeIcon: `
- <svg aria-label="Close menu" class="octicon octicon-x" width="16" height="16" role="img">
- <path fill-rule="evenodd" d="M3.72 3.72a.75.75 0 011.06 0L8 6.94l3.22-3.22a.75.75 0 111.06 1.06L9.06 8l3.22 3.22a.75.75 0 11-1.06 1.06L8 9.06l-3.22 3.22a.75.75 0 01-1.06-1.06L6.94 8 3.72 4.78a.75.75 0 010-1.06z"></path>
- </svg>`,
- copyIcon: `
- <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-copy js-clipboard-copy-icon d-inline-block">
- <path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path>
- </svg>`,
- copiedIcon: `
- <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-check js-clipboard-check-icon color-fg-success d-inline-block d-sm-none">
- <path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"></path>
- </svg>`,
- };
-
- const clonePrefix = "git clone ";
- const depthPrefix = "--depth=1 ";
- let message;
- let settingHtml;
-
- main();
- $(document).on("pjax:success", function () {
- $("#mirror-menu").remove();
- main();
- });
-
- function main() {
- GM_addStyle(style);
- logInfo(GM_info.script.name, GM_info.script.version);
- const prefix = getClonePrefix();
- addMenu(prefix);
- setTimeout(() => {
- addHttpsClone(prefix);
- addSSHClone(prefix);
- addRawList();
- }, 1000);
- if (isPC()) {
- addDownloadZip();
- }
-
- // The Release page loads element dynamically.
- const callback = (mutationsList, observer) => {
- if (location.pathname.indexOf("/releases") === -1) return;
- for (const mutation of mutationsList) {
- for (const target of mutation.addedNodes) {
- if (target.nodeType !== 1) return;
- if (
- target.tagName === "DIV" &&
- target.dataset.viewComponent === "true" &&
- target.classList[0] === "Box"
- )
- addReleasesList();
- }
- }
- };
- const observer = new MutationObserver(callback);
- observer.observe(document, { childList: true, subtree: true });
- }
-
- /**
- * Initialize setting.
- */
- function initSetting() {
- let lang = GM_getValue("lang");
- let clone = GM_getValue("clone");
- let depth = GM_getValue("depth");
- if (lang == undefined) {
- GM_setValue("lang", "zh");
- }
- if (clone == undefined) {
- GM_setValue("clone", true);
- }
- if (depth == undefined) {
- GM_setValue("depth", true);
- }
- }
-
- function addMenu(prefix) {
- initSetting();
- message = getMessage(true, true);
- settingHtml = getSettingHtml();
- let menuButtonHtml =
- getMenuButtonPrefix() + getCloneList(prefix) + getBrowseList() + getMenuButtonSuffix();
- $("div.d-flex.flex-wrap.flex-items-center.wb-break-word.f3.text-normal").append(menuButtonHtml);
- }
-
- function getMenuButtonPrefix() {
- return `
- <details class="details-reset details-overlay mr-0 mb-0" id="mirror-menu">
- <summary class="btn ml-2 btn-primary" id="menuButtonTitle" data-hotkey="m" title="${message.menuButton.title}" aria-haspopup="menu" role="button">
- <span class="css-truncate-target" id="menuButtonName" data-menu-button="">${message.menuButton.name}</span>
- <span class="dropdown-caret"></span>
- </summary>
-
- <details-menu class="SelectMenu SelectMenu--hasFilter" role="menu">
- <div class="SelectMenu-modal menuContainer">
- <header class="SelectMenu-header">
- <span class="SelectMenu-title" id="menuButtonHeader">${message.menuButton.header}</span>
- ${settingHtml}
- <button class="SelectMenu-closeButton" type="button" data-toggle-for="mirror-menu">
- ${icons.closeIcon}
- </button>
- </header>
- <tab-container class="d-flex flex-column js-branches-tags-tabs" style="min-height: 0;">
- <div role="tabpanel" class="d-flex flex-column flex-auto" tabindex="0">
- <div class="btn-block flash-error menuBlock" id="menuButtonBlock" role="alert">
- ${message.menuButton.block}
- </div>
- <div class="SelectMenu-list" data-filter-list="">`;
- }
-
- function getSettingHtml() {
- const clone = GM_getValue("clone");
- const depth = GM_getValue("depth");
- const lang = GM_getValue("lang");
- const cloneStatus = clone && clone != "undefined" ? " checked" : "";
- const depthStatus = depth && depth != "undefined" ? " checked" : "";
- const langStatus = lang == "en" ? " checked" : "";
- return `
- <label class="menuButtonLabel"><input id="menuButtonClone" class="menuButtonCheck" type="checkbox"${cloneStatus}>Clone</input></label>
- <label class="menuButtonLabel"><input id="menuButtonDepth" class="menuButtonCheck" type="checkbox"${depthStatus}>Depth</input></label>
- <label class="menuButtonLabel"><input id="menuButtonLang" class="menuButtonCheck" type="checkbox"${langStatus}>English</input></label>
- `;
- }
-
- /**
- * Clone Checkbox event.
- */
- $("#menuButtonClone").change(function () {
- const status = $("#menuButtonClone").is(":checked");
- GM_setValue("clone", status);
- if (!status) {
- document.getElementById("menuButtonDepth").checked = false;
- depthChanged(status);
- }
- cloneChanged(status);
- });
-
- /**
- * Depth Checkbox event.
- */
- $("#menuButtonDepth").change(function () {
- const status = $("#menuButtonDepth").is(":checked");
- depthChanged(status);
- console.log(status);
- let cloneStatus = $("#menuButtonClone").is(":checked");
- if (status && !cloneStatus) {
- cloneStatus = true;
- document.getElementById("menuButtonClone").checked = true;
- GM_setValue("clone", cloneStatus);
- cloneChanged(cloneStatus);
- }
- });
-
- /**
- * Language Checkbox event.
- */
- $("#menuButtonLang").change(function () {
- const status = $("#menuButtonLang").is(":checked");
- const value = status ? "en" : "zh";
- GM_setValue("lang", value);
- message = getMessage();
- updateMessage();
- });
-
- function cloneChanged(status) {
- const inputs = $("input.clone");
- for (let i = 0; i < inputs.length; i++) {
- let value = inputs[i].value;
- if (status) {
- value = clonePrefix + value;
- } else {
- value = value.replace(clonePrefix, "");
- }
- inputs[i].value = value;
- $(inputs[i]).next().children().attr("value", value);
- }
- }
-
- function depthChanged(status) {
- GM_setValue("depth", status);
- const inputs = $(".form-control.input-monospace.input-sm.clone");
- const index = clonePrefix.length;
- for (let i = 0; i < inputs.length; i++) {
- let value = inputs[i].value;
- if (status) {
- const length = value.length;
- if (value.startsWith(clonePrefix)) {
- value = value.slice(0, index) + depthPrefix + value.slice(index, length);
- } else {
- value = depthPrefix + value;
- }
- } else {
- value = value.replace(depthPrefix, "");
- }
- inputs[i].value = value;
- $(inputs[i]).next().children().attr("value", value);
- }
- }
-
- /**
- * Update message by target language.
- */
- function updateMessage() {
- $("#menuButtonTitle").attr("title", message.menuButton.title);
- $("#menuButtonName").html(message.menuButton.name);
- $("#menuButtonHeader").html(message.menuButton.header);
- $("#menuButtonBlock").html(message.menuButton.block);
- }
-
- function addHttpsClone(prefix) {
- let httpsGroup = document.querySelector('[role="tabpanel"]:nth-child(2) div.input-group');
- if (!httpsGroup) {
- return;
- }
- let inputs = httpsGroup.querySelectorAll("input.clone");
- if (inputs.length > 0) {
- return;
- }
- updateDefaultClone(prefix, httpsGroup);
- httpsGroup.insertAdjacentHTML("afterend", getCloneList(prefix));
- }
-
- /**
- * Get the clone list.
- */
- function getCloneList(prefix) {
- const href = window.location.href.split("/");
- const git = href[3] + "/" + href[4] + ".git";
- let menuButtonHtml = "";
- cloneSet.forEach(id => {
- menuButtonHtml += getCloneHtml(prefix + mirrors[id]["url"] + "/" + git, mirrors[id]["name"]);
- });
- return menuButtonHtml;
- }
-
- function updateDefaultClone(prefix, parent) {
- let input = parent.querySelector("input");
- gitStr = input.value;
- gitNew = prefix + gitStr;
- let button = parent.querySelector("clipboard-copy");
- input.setAttribute("value", gitNew);
- input.setAttribute("aria-label", gitNew);
- input.className += " clone";
- button.setAttribute("value", gitNew);
- }
-
- function getMenuButtonSuffix() {
- return `</div></div></tab-container></div></details-menu></details>`;
- }
-
- function addSSHClone(prefix) {
- let sshGroup = document.querySelector('[role="tabpanel"]:nth-child(3) div.input-group');
- if (!sshGroup) {
- return;
- }
-
- let inputs = sshGroup.querySelectorAll("input.clone");
- if (inputs.length > 0) {
- return;
- }
-
- let defaultSsh = sshGroup.firstElementChild;
- const sshStr = defaultSsh.value;
- let hrefSplit = sshStr.split(":");
- let groupHtml = "";
-
- if (hrefSplit[0] != "git@github.com") {
- return;
- }
- defaultSsh.value = prefix + sshStr;
- defaultSsh.setAttribute("aria-label", prefix + sshStr);
- defaultSsh.className += " clone";
-
- let button = sshGroup.querySelector("clipboard-copy");
- button.setAttribute("value", prefix + sshStr);
-
- sshSet.forEach(id => {
- groupHtml += getCloneHtml(prefix + mirrors[id]["url"] + hrefSplit[1], mirrors[id]["name"]);
- });
- sshGroup.insertAdjacentHTML("afterend", groupHtml);
- }
-
- /**
- * Get the clone command prefix.
- */
- function getClonePrefix() {
- let prefix = "";
- let clone = GM_getValue("clone");
- let depth = GM_getValue("depth");
- if (clone) {
- prefix += "git clone ";
- }
- if (depth) {
- prefix += "--depth=1 ";
- }
- return prefix;
- }
-
- /**
- * Get the clone button html string.
- * @param {String} url url.
- * @param {tip} tip tip.
- */
- function getCloneHtml(url, tip) {
- return `
- <div class="input-group" style="margin-top: 4px;" title="${tip}">
- <input type="text" class="clone form-control input-monospace input-sm color-bg-subtle" data-autoselect="" value="${url}" aria-label="${url}" readonly="">
- <div class="input-group-button">
- <clipboard-copy value="${url}" class="btn btn-sm js-clipboard-copy tooltipped-no-delay ClipboardButton" tabindex="0" role="button">
- ${icons.copyIcon}${icons.copiedIcon}
- </clipboard-copy>
- </div>
- </div>`;
- }
-
- /**
- * Get the browse list.
- */
- function getBrowseList() {
- let menuButtonHtml = ``;
- const href = window.location.href.split("/");
- const path = window.location.pathname;
- browseSet.forEach(id => {
- menuButtonHtml += getBrowseHtml(
- mirrors[id]["url"] + path,
- mirrors[id]["name"],
- mirrors[id]["description"]
- );
- });
- if (href.length == 5 || path.includes("/tree/") || path.includes("/blob/")) {
- var html = mirrors[5]["url"] + path.replace("/tree/", "@").replace("/blob/", "@");
- if (!path.includes("/blob/")) {
- html += "/";
- }
- menuButtonHtml += getBrowseHtml(html, mirrors[5]["name"], mirrors[5]["description"]);
- }
- if (location.hostname != "github.com") {
- menuButtonHtml += getBrowseHtml(`https://github.com${path}`, "返回GitHub");
- }
- return menuButtonHtml;
- }
-
- /**
- * Get browse html string.
- * @param {String} url url.
- * @param {String} name name.
- * @param {String} tip tip.
- * @returns
- */
- function getBrowseHtml(url, name, tip = "") {
- return `
- <a class="SelectMenu-item" href="${url}" target="_blank" title="${tip}" role="menuitemradio" aria-checked="false" rel="nofollow">
- <span class="css-truncate css-truncate-overflow" style="width: 520px; overflow: hidden; word-break:keep-all; white-space:nowrap; text-overflow:ellipsis;">${url}</span>
- <span class="css-truncate css-truncate-overflow" style="width: 80px; text-align: right;">${name}</span>
- </a>`;
- }
-
- /**
- * Add Release list.
- */
- function addReleasesList() {
- $(".Box--condensed")
- .find("[href]")
- .each(function () {
- if ($(this).parent().parent().find(".download-box").length == 0) {
- const href = $(this).attr("href");
- $(this)
- .parent()
- .after(`<div class="Box-body download-box" >` + getReleaseDownloadHtml(href) + `</div>`);
- $(this).parent().removeClass("Box-body");
- }
- });
- }
-
- /**
- * Get Release download button html string.
- * @param {String} href href.
- * @returns html.
- */
- function getReleaseDownloadHtml(href) {
- let html = "";
- downloadSet.forEach(id => {
- html += `<a class="flex-1 btn btn-outline get-repo-btn" rel="nofollow" href="${mirrors[id]["url"] + href
- }" title="${mirrors[id]["description"]}">${mirrors[id]["name"]}</a>`;
- });
- return html;
- }
-
- /**
- * Add download zip button.
- */
- function addDownloadZip() {
- $("a[data-open-app='link']").each(function () {
- var li = $(`<li class="Box-row p-0"></li>`);
- const downloadHref = $(this).attr("href");
- var aElement = $(this)
- .clone()
- .removeAttr("data-hydro-click data-hydro-click-hmac data-ga-click");
- aElement.addClass("Box-row Box-row--hover-gray");
- downloadSet.forEach(id => {
- let tempA = aElement.clone();
- tempA.attr({
- href: mirrors[id]["url"] + downloadHref,
- title: mirrors[id]["description"],
- });
- tempA.html(tempA.html().replace("Download ZIP", `Download ZIP(${mirrors[id]["name"]})`));
- li = li.clone().append(tempA);
- });
- $(this).parent().after(li);
- });
- }
-
- /**
- * Add Raw list.
- */
- function addRawList() {
- let rawButton = $('#raw-url, a[data-testid="raw-button"]');
- if (rawButton.length == 0) {
- return;
- }
- const href = rawButton.attr("href");
- rawSet.forEach(id => {
- if (id == 3 || id == 10) {
- addRawButton(id, mirrors[id]["url"] + href.replace("/raw", ""), rawButton);
- } else if (id == 6) {
- addRawButton(id, mirrors[id]["url"] + href.replace("/raw/", "@"), rawButton);
- } else {
- addRawButton(id, mirrors[id]["url"] + href, rawButton);
- }
- });
- }
-
- /**
- * Add the Raw Button.
- * @param {Number} id id of mirrors.
- * @param {String} url url.
- * @param {Object} rawButton the raw button.
- */
- function addRawButton(id, url, rawButton) {
- var span = rawButton.clone().removeAttr("id");
- span.attr({
- href: url,
- title: mirrors[id]["description"],
- target: "_blank",
- });
- span.text(mirrors[id]["name"]);
- rawButton.before(span);
- }
-
- /**
- * Get message by setting.
- */
- function getMessage() {
- return "zh" == GM_getValue("lang") ? messages.zh : messages.en;
- }
-
- /**
- * Log the title and version at the front of the console.
- * @param {String} title title.
- * @param {String} version script version.
- */
- function logInfo(title, version) {
- const titleStyle = "color:white;background-color:#606060";
- const versionStyle = "color:white;background-color:#1475b2";
- const logTitle = " " + title + " ";
- const logVersion = " " + version + " ";
- console.log("%c" + logTitle + "%c" + logVersion, titleStyle, versionStyle);
- }
-
- /**
- * Check if the visitor is PC.
- */
- function isPC() {
- var userAgentInfo = navigator.userAgent;
- var agents = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"];
- var isPC = true;
- const len = agents.length;
- for (var v = 0; v < len; v++) {
- if (userAgentInfo.indexOf(agents[v]) > 0) {
- isPC = false;
- break;
- }
- }
- return isPC;
- }
- })();