Skip to content

Commit ac5970e

Browse files
authored
feat: GitHub Mirror
1 parent 57a724d commit ac5970e

File tree

5 files changed

+325
-118
lines changed

5 files changed

+325
-118
lines changed
Lines changed: 310 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
1+
(function () {
2+
const isDotGit = (url) =>
3+
url.indexOf("https://github.com/") === 0 && /\.git$/.test(url);
4+
5+
const isReleaseDownload = (url) => url.includes("/releases/download/");
6+
const rawRegex = /https?:\/\/(.*?)\/(.*?)\/(.*?)\/raw\//;
7+
const isRAW = (url) => rawRegex.test(url);
8+
const isArchive = (url) => url.includes("/archive/refs/");
9+
10+
const handler = {
11+
"gh-proxy-main": (url) => {
12+
if (
13+
isReleaseDownload(url) ||
14+
isArchive(url) ||
15+
isRAW(url) ||
16+
isDotGit(url)
17+
) {
18+
return `https://gh-proxy.org/${url}`;
19+
}
20+
},
21+
"gh-proxy-hk": (url) => {
22+
if (
23+
isReleaseDownload(url) ||
24+
isArchive(url) ||
25+
isRAW(url) ||
26+
isDotGit(url)
27+
) {
28+
return `https://hk.gh-proxy.org/${url}`;
29+
}
30+
},
31+
ghfast: (url) => {
32+
if (
33+
isReleaseDownload(url) ||
34+
isArchive(url) ||
35+
isRAW(url) ||
36+
isDotGit(url)
37+
) {
38+
return `https://ghfast.top/${url}`;
39+
}
40+
},
41+
"custom-hubproxy": (url) => {
42+
if (
43+
isReleaseDownload(url) ||
44+
isArchive(url) ||
45+
isRAW(url) ||
46+
isDotGit(url)
47+
) {
48+
return `${GM_getValue("prefix")}/${url}`;
49+
}
50+
},
51+
};
52+
53+
const openSetting = () => {
54+
// 创建遮罩层
55+
const overlay = document.createElement("div");
56+
overlay.style.cssText = `
57+
position: fixed;
58+
top: 0;
59+
left: 0;
60+
width: 100%;
61+
height: 100%;
62+
background-color: rgba(0,0,0,0.5);
63+
z-index: 10000;
64+
display: flex;
65+
justify-content: center;
66+
align-items: center;
67+
`;
68+
69+
// 创建弹窗容器
70+
const modal = document.createElement("div");
71+
modal.style.cssText = `
72+
background: white;
73+
border-radius: 8px;
74+
padding: 20px;
75+
min-width: 400px;
76+
max-width: 500px;
77+
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
78+
`;
79+
80+
// 创建标题
81+
const title = document.createElement("h3");
82+
title.textContent = "代理设置";
83+
title.style.cssText = "margin-top: 0; margin-bottom: 15px;";
84+
85+
// 创建代理选项
86+
const optionsContainer = document.createElement("div");
87+
optionsContainer.style.marginBottom = "15px";
88+
89+
const proxyOptions = [
90+
{ value: "gh-proxy-main", label: "gh-proxy-main" },
91+
{ value: "gh-proxy-hk", label: "gh-proxy-hk" },
92+
{ value: "ghfast", label: "ghfast" },
93+
{ value: "custom-hubproxy", label: "Custom Hub Proxy" },
94+
];
95+
96+
const selectedHandler = GM_getValue("handler") || "gh-proxy-main";
97+
let currentSelection = selectedHandler;
98+
99+
proxyOptions.forEach((option) => {
100+
const radioDiv = document.createElement("div");
101+
radioDiv.style.cssText =
102+
"margin-bottom: 10px; display: flex; align-items: center;";
103+
104+
const radio = document.createElement("input");
105+
radio.type = "radio";
106+
radio.name = "proxy-option";
107+
radio.value = option.value;
108+
radio.checked = option.value === selectedHandler;
109+
radio.addEventListener("change", (e) => {
110+
currentSelection = e.target.value;
111+
updatePrefixVisibility();
112+
});
113+
114+
const label = document.createElement("label");
115+
label.textContent = option.label;
116+
label.style.marginLeft = "8px";
117+
118+
radioDiv.appendChild(radio);
119+
radioDiv.appendChild(label);
120+
optionsContainer.appendChild(radioDiv);
121+
});
122+
123+
// 创建prefix输入框
124+
const prefixContainer = document.createElement("div");
125+
prefixContainer.id = "prefix-container";
126+
prefixContainer.style.cssText = "margin-bottom: 15px;";
127+
128+
const prefixLabel = document.createElement("label");
129+
prefixLabel.textContent = "Prefix (以http或https开头):";
130+
prefixLabel.style.display = "block";
131+
prefixLabel.style.marginBottom = "5px";
132+
133+
const prefixInput = document.createElement("input");
134+
prefixInput.type = "text";
135+
prefixInput.placeholder = "例如: https://your-proxy.com";
136+
prefixInput.style.cssText = `
137+
width: 100%;
138+
padding: 8px;
139+
border: 1px solid #ccc;
140+
border-radius: 4px;
141+
box-sizing: border-box;
142+
`;
143+
144+
// 设置当前值
145+
const currentPrefix = GM_getValue("prefix", "");
146+
if (currentSelection === "custom-hubproxy") {
147+
prefixInput.value = currentPrefix;
148+
}
149+
150+
prefixContainer.appendChild(prefixLabel);
151+
prefixContainer.appendChild(prefixInput);
152+
153+
// 更新prefix可见性
154+
const updatePrefixVisibility = () => {
155+
prefixContainer.style.display =
156+
currentSelection === "custom-hubproxy" ? "block" : "none";
157+
};
158+
159+
// 初始化时调用一次
160+
updatePrefixVisibility();
161+
162+
// 创建按钮容器
163+
const buttonContainer = document.createElement("div");
164+
buttonContainer.style.cssText =
165+
"display: flex; justify-content: flex-end; gap: 10px;";
166+
167+
// 创建取消按钮
168+
const cancelButton = document.createElement("button");
169+
cancelButton.textContent = "取消";
170+
cancelButton.style.cssText = `
171+
padding: 8px 16px;
172+
background: #f0f0f0;
173+
border: 1px solid #ccc;
174+
border-radius: 4px;
175+
cursor: pointer;
176+
`;
177+
cancelButton.addEventListener("click", () => {
178+
document.body.removeChild(overlay);
179+
});
180+
181+
// 创建保存按钮
182+
const saveButton = document.createElement("button");
183+
saveButton.textContent = "保存";
184+
saveButton.style.cssText = `
185+
padding: 8px 16px;
186+
background: #0969da;
187+
color: white;
188+
border: none;
189+
border-radius: 4px;
190+
cursor: pointer;
191+
`;
192+
193+
// 验证函数
194+
const validateInputs = () => {
195+
if (currentSelection === "custom-hubproxy") {
196+
const prefixValue = prefixInput.value.trim();
197+
return prefixValue && /^(https?:\/\/)/.test(prefixValue);
198+
}
199+
return true;
200+
};
201+
202+
// 保存按钮事件
203+
saveButton.addEventListener("click", () => {
204+
if (!validateInputs()) {
205+
alert("请提供有效的Prefix(以http或https开头)");
206+
return;
207+
}
208+
209+
// 保存选择的代理
210+
GM_setValue("handler", currentSelection);
211+
212+
// 如果是自定义代理,保存prefix
213+
if (currentSelection === "custom-hubproxy") {
214+
GM_setValue("prefix", prefixInput.value.trim());
215+
}
216+
217+
// 关闭弹窗
218+
document.body.removeChild(overlay);
219+
220+
// 刷新页面以应用新设置
221+
location.reload();
222+
});
223+
224+
// 添加按钮到容器
225+
buttonContainer.appendChild(cancelButton);
226+
buttonContainer.appendChild(saveButton);
227+
228+
// 组装弹窗内容
229+
modal.appendChild(title);
230+
modal.appendChild(optionsContainer);
231+
modal.appendChild(prefixContainer);
232+
modal.appendChild(buttonContainer);
233+
234+
// 添加弹窗到遮罩
235+
overlay.appendChild(modal);
236+
237+
// 添加遮罩到页面
238+
document.body.appendChild(overlay);
239+
};
240+
241+
const replaceAnchor = (anchor) => {
242+
if (anchor.classList.contains("x_hacked")) {
243+
return;
244+
}
245+
anchor.classList.add("x_hacked");
246+
const replaceUrl = handler[GM_getValue("handler") || "gh-proxy-main"];
247+
const newUrl = replaceUrl(anchor.href);
248+
if (newUrl) {
249+
console.log("[GitHub Mirror] 将 " + anchor.href + " 替换为 " + newUrl);
250+
anchor.href = newUrl;
251+
}
252+
};
253+
254+
const replaceAnchors = (anchors) =>
255+
Array.prototype.forEach.call(anchors, replaceAnchor);
256+
257+
const checkTasks = [
258+
() => {
259+
// Download ZIP
260+
const modalLink = document.querySelector('ul[class^="prc-ActionList-ActionList"]');
261+
if (modalLink) {
262+
replaceAnchors(modalLink.querySelectorAll("a"));
263+
}
264+
// HTTPS Clone
265+
const e = document.querySelector('#clone-with-https');
266+
if (e && !e.classList.contains('x_hacked')) {
267+
e.classList.add("x_hacked");
268+
const replaceUrl = handler[GM_getValue("handler") || "gh-proxy-main"];
269+
const newUrl = replaceUrl(e.value);
270+
if (newUrl) {
271+
console.log("[GitHub Mirror] 将 " + e.value + " 替换为 " + newUrl);
272+
e.value = newUrl;
273+
}
274+
}
275+
},
276+
() => {
277+
// Release 下载页面
278+
const el = document.querySelectorAll(".Box-footer ul li a");
279+
if (el.length === 0) return;
280+
replaceAnchors(el);
281+
},
282+
() => {
283+
// Tags 页面
284+
const desc = document.querySelector(".commit-desc");
285+
if (!desc) return;
286+
replaceAnchors(document.querySelectorAll(".Details a"));
287+
},
288+
() => {
289+
// Raw 按钮
290+
const raw = document.querySelector('a[data-testid="raw-button"]');
291+
if (!raw) return;
292+
replaceAnchor(raw);
293+
},
294+
];
295+
296+
const doCheck = () => checkTasks.forEach((x) => x());
297+
298+
function main() {
299+
GM_registerMenuCommand("设置", () => openSetting());
300+
const observer = new MutationObserver(doCheck);
301+
observer.observe(document.body, {
302+
childList: true,
303+
attributes: true,
304+
subtree: true,
305+
});
306+
doCheck();
307+
}
308+
309+
main();
310+
})();

GitHub_Mirror/README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# GitHub Mirror
2+
3+
自动替换 GitHub 页面上的相关链接到镜像站
4+
5+
注意:此脚本仅替换链接,镜像服务非脚本作者维护。
6+
7+
[【点击安装】](https://userscript.firefoxcn.net/js/GitHub_Mirror.user.js)
8+
9+
author: 泷涯
Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1-
name: GitHub with FastGit
2-
version: 2
3-
description: 自动替换 GitHub 页面上的相关链接为 FastGit
1+
name: GitHub Mirror
2+
version: 1
3+
description: 自动替换 GitHub 页面上的相关链接到镜像站
44
include:
55
- https://github.com/*
66
author: ShuangYa
77
run-at: document-end
88
icon: https://github.githubassets.com/favicons/favicon.png
99
grant:
1010
- unsafeWindow
11+
- GM_registerMenuCommand
12+
- GM_setValue
13+
- GM_getValue
1114
namespace: blog.sylingd.com

0 commit comments

Comments
 (0)