fix: browser extension Clone/Download acceleration, use GitHub proxy routes

This commit is contained in:
violettools
2026-01-23 23:14:57 +08:00
parent 2aa7edf9bb
commit 85cffd9f91
4 changed files with 284 additions and 89 deletions

5
.gitignore vendored
View File

@@ -41,3 +41,8 @@ media/videos/1080p60/CameraFollowCursorCVScene.mp4
media/images/ media/images/
media/text/ media/text/
media/videos/1080p60/partial_movie_files/ media/videos/1080p60/partial_movie_files/
# 大视频文件
cfspider教程.mp4
*.mp4
!media/videos/1080p60/CameraFollowCursorCV.mp4

View File

@@ -78,3 +78,35 @@
box-shadow: 0 4px 12px rgba(0, 212, 255, 0.3); box-shadow: 0 4px 12px rgba(0, 212, 255, 0.3);
} }
/* Clone URL 加速切换按钮 */
.cfspider-accel-toggle {
display: inline-flex;
align-items: center;
margin-left: 8px;
padding: 4px 10px;
background: linear-gradient(135deg, #00d4ff 0%, #7b2cbf 100%);
color: #fff !important;
font-size: 12px;
font-weight: 500;
border: none;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s ease;
white-space: nowrap;
box-shadow: 0 2px 8px rgba(0, 212, 255, 0.3);
}
.cfspider-accel-toggle:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 212, 255, 0.4);
}
.cfspider-accel-toggle.active {
background: linear-gradient(135deg, #3fb950 0%, #238636 100%);
box-shadow: 0 2px 8px rgba(63, 185, 80, 0.3);
}
.cfspider-accel-toggle.active:hover {
box-shadow: 0 4px 12px rgba(63, 185, 80, 0.4);
}

View File

@@ -3,7 +3,7 @@
(function() { (function() {
'use strict'; 'use strict';
let config = { workersUrl: '', uuid: '', enabled: true }; let config = { workersUrl: '', uuid: '', enabled: true, cloneAccel: true, downloadAccel: true };
// GitHub 下载链接匹配模式 // GitHub 下载链接匹配模式
const GITHUB_DOWNLOAD_PATTERNS = [ const GITHUB_DOWNLOAD_PATTERNS = [
@@ -21,21 +21,60 @@
return GITHUB_DOWNLOAD_PATTERNS.some(pattern => pattern.test(url)); return GITHUB_DOWNLOAD_PATTERNS.some(pattern => pattern.test(url));
} }
// 生成加速链接 // 检查是否是 Git clone 链接
function generateAcceleratedUrl(originalUrl) { function isGitCloneLink(url) {
if (!config.workersUrl || !config.uuid) return null; if (!url) return false;
if (url.includes('github.com/') && !url.includes('/releases/') && !url.includes('/blob/') && !url.includes('/tree/')) {
// 构建代理 URL const match = url.match(/github\.com\/([^\/]+)\/([^\/\?#]+)/);
const workersHost = config.workersUrl.replace(/^https?:\/\//, '').replace(/\/$/, ''); if (match && match[1] && match[2]) {
const encodedUrl = encodeURIComponent(originalUrl); return true;
}
// 使用 /proxy API }
return `https://${workersHost}/proxy?url=${encodedUrl}&method=GET`; return false;
} }
// 创建加速按钮 // 生成加速的 clone 链接
function generateAcceleratedCloneUrl(originalUrl) {
if (!config.workersUrl) return null;
const workersHost = config.workersUrl.replace(/^https?:\/\//, '').replace(/\/$/, '');
const githubPath = originalUrl.replace('https://github.com/', '');
return `https://${workersHost}/github/${githubPath}`;
}
// 生成加速链接(下载)- 使用专门的 GitHub 代理路由
function generateAcceleratedUrl(originalUrl) {
if (!config.workersUrl) return null;
const workersHost = config.workersUrl.replace(/^https?:\/\//, '').replace(/\/$/, '');
// 根据不同的 GitHub 域名使用不同的代理路由
if (originalUrl.includes('raw.githubusercontent.com/')) {
const path = originalUrl.replace('https://raw.githubusercontent.com/', '');
return `https://${workersHost}/gh-raw/${path}`;
}
if (originalUrl.includes('codeload.github.com/')) {
const path = originalUrl.replace('https://codeload.github.com/', '');
return `https://${workersHost}/gh-codeload/${path}`;
}
if (originalUrl.includes('objects.githubusercontent.com/')) {
const path = originalUrl.replace('https://objects.githubusercontent.com/', '');
return `https://${workersHost}/gh-objects/${path}`;
}
if (originalUrl.includes('github.com/')) {
const path = originalUrl.replace('https://github.com/', '');
return `https://${workersHost}/github/${path}`;
}
return null;
}
// 创建下载加速按钮
function createAccelerateButton(link) { function createAccelerateButton(link) {
// 检查是否已添加按钮
if (link.nextElementSibling?.classList.contains('cfspider-btn')) return; if (link.nextElementSibling?.classList.contains('cfspider-btn')) return;
if (link.parentElement.querySelector('.cfspider-btn')) return; if (link.parentElement.querySelector('.cfspider-btn')) return;
@@ -51,59 +90,23 @@
btn.href = generateAcceleratedUrl(link.href) || '#'; btn.href = generateAcceleratedUrl(link.href) || '#';
btn.target = '_blank'; btn.target = '_blank';
// 点击事件
btn.addEventListener('click', async (e) => { btn.addEventListener('click', async (e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
if (!config.workersUrl || !config.uuid) { if (!config.workersUrl) {
alert('请先在 CFspider 扩展中配置 Workers 地址和 UUID'); alert('请先在 CFspider 扩展中配置 Workers 地址');
return; return;
} }
// 开始下载
const acceleratedUrl = generateAcceleratedUrl(link.href); const acceleratedUrl = generateAcceleratedUrl(link.href);
if (acceleratedUrl) { if (acceleratedUrl) {
// 显示加载状态 window.open(acceleratedUrl, '_blank');
btn.classList.add('loading'); } else {
btn.innerHTML = ` alert('不支持加速此链接类型');
<svg class="spinner" viewBox="0 0 16 16" width="14" height="14" fill="currentColor">
<path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Z" opacity="0.3"/>
<path d="M8 0a8 8 0 0 1 8 8h-1.5A6.5 6.5 0 0 0 8 1.5V0Z"/>
</svg>
加速中...
`;
try {
// 直接打开加速链接
window.open(acceleratedUrl, '_blank');
setTimeout(() => {
btn.classList.remove('loading');
btn.innerHTML = `
<svg viewBox="0 0 16 16" width="14" height="14" fill="currentColor">
<path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm4.879-2.773 4.264 2.559a.25.25 0 0 1 0 .428l-4.264 2.559A.25.25 0 0 1 6 10.559V5.442a.25.25 0 0 1 .379-.215Z"/>
</svg>
加速
`;
}, 1500);
} catch (err) {
console.error('CFspider 加速失败:', err);
btn.classList.remove('loading');
btn.innerHTML = '失败';
setTimeout(() => {
btn.innerHTML = `
<svg viewBox="0 0 16 16" width="14" height="14" fill="currentColor">
<path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm4.879-2.773 4.264 2.559a.25.25 0 0 1 0 .428l-4.264 2.559A.25.25 0 0 1 6 10.559V5.442a.25.25 0 0 1 .379-.215Z"/>
</svg>
加速
`;
}, 2000);
}
} }
}); });
// 插入按钮
if (link.parentElement.classList.contains('d-flex') || if (link.parentElement.classList.contains('d-flex') ||
link.closest('.Box-row') || link.closest('.Box-row') ||
link.closest('.release-main-section')) { link.closest('.release-main-section')) {
@@ -115,7 +118,7 @@
// 扫描并处理下载链接 // 扫描并处理下载链接
function scanDownloadLinks() { function scanDownloadLinks() {
if (!config.enabled) return; if (!config.enabled || !config.downloadAccel) return;
const links = document.querySelectorAll('a[href]'); const links = document.querySelectorAll('a[href]');
@@ -126,28 +129,178 @@
}); });
} }
// 处理 Clone URL - 直接替换输入框内容和复制按钮
function processCloneUrl() {
if (!config.enabled || !config.cloneAccel || !config.workersUrl) return;
const selectors = [
'input[readonly]',
'input[data-autoselect]',
'input.form-control[readonly]',
'input[aria-label*="clone"]',
'input[aria-label*="Clone"]',
'.input-group input',
'[data-target="get-repo-modal.cloneInput"]'
];
let cloneInputs = [];
selectors.forEach(sel => {
const inputs = document.querySelectorAll(sel);
inputs.forEach(i => {
if (!cloneInputs.includes(i)) cloneInputs.push(i);
});
});
cloneInputs.forEach(input => {
if (input.dataset.cfspiderProcessed) return;
const value = input.value;
if (!isGitCloneLink(value)) return;
input.dataset.cfspiderProcessed = 'true';
const originalUrl = value;
const acceleratedUrl = generateAcceleratedCloneUrl(originalUrl);
if (!acceleratedUrl) return;
const inputGroup = input.closest('.input-group') || input.parentElement;
// 创建加速按钮
const accelBtn = document.createElement('button');
accelBtn.className = 'cfspider-accel-toggle';
accelBtn.type = 'button';
accelBtn.innerHTML = '⚡ 加速';
accelBtn.title = '点击切换为 CFspider 加速链接';
// 存储状态到 input 元素上
input._cfspiderState = { original: originalUrl, accelerated: acceleratedUrl, isAccelerated: false };
accelBtn.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
const state = input._cfspiderState;
if (!state.isAccelerated) {
input.value = acceleratedUrl;
accelBtn.innerHTML = '✓ 已加速';
accelBtn.classList.add('active');
state.isAccelerated = true;
} else {
input.value = originalUrl;
accelBtn.innerHTML = '⚡ 加速';
accelBtn.classList.remove('active');
state.isAccelerated = false;
}
input.select();
});
input.insertAdjacentElement('afterend', accelBtn);
// 拦截容器内所有按钮的点击(可能是复制按钮)
interceptCopyButtons(inputGroup, input);
});
}
// 拦截复制按钮
function interceptCopyButtons(container, input) {
let searchContainer = container;
for (let i = 0; i < 3; i++) {
if (searchContainer.parentElement) {
searchContainer = searchContainer.parentElement;
}
}
const buttons = searchContainer.querySelectorAll('button, [role="button"]');
buttons.forEach(btn => {
const ariaLabel = btn.getAttribute('aria-label') || '';
const title = btn.getAttribute('title') || '';
const text = btn.textContent || '';
if (ariaLabel.toLowerCase().includes('copy') ||
title.toLowerCase().includes('copy') ||
text.toLowerCase().includes('copy') ||
btn.querySelector('svg.octicon-copy')) {
btn.addEventListener('click', (e) => {
const state = input._cfspiderState;
if (state && state.isAccelerated) {
e.stopImmediatePropagation();
e.preventDefault();
navigator.clipboard.writeText(state.accelerated).then(() => {
showCopySuccess(btn);
});
return false;
}
}, true);
}
});
}
// 显示复制成功
function showCopySuccess(btn) {
const originalHTML = btn.innerHTML;
btn.innerHTML = '<svg viewBox="0 0 16 16" width="16" height="16" fill="#3fb950"><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>';
setTimeout(() => {
btn.innerHTML = originalHTML;
}, 1500);
}
// 加载配置 // 加载配置
async function loadConfig() { async function loadConfig() {
try { try {
const result = await chrome.storage.sync.get(['workersUrl', 'uuid', 'enabled']); const result = await chrome.storage.sync.get(['workersUrl', 'uuid', 'enabled', 'cloneAccel', 'downloadAccel']);
config = { config.workersUrl = result.workersUrl || '';
workersUrl: result.workersUrl || '', config.uuid = result.uuid || '';
uuid: result.uuid || '', config.enabled = result.enabled !== false;
enabled: result.enabled !== false config.cloneAccel = result.cloneAccel !== false;
}; config.downloadAccel = result.downloadAccel !== false;
} catch (e) { } catch (e) {
console.error('CFspider: 加载配置失败', e); console.error('CFspider: 加载配置失败', e);
} }
} }
// 监听配置更新 // 监听配置变化
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { chrome.storage.onChanged.addListener((changes) => {
if (message.type === 'CONFIG_UPDATED') { if (changes.workersUrl) config.workersUrl = changes.workersUrl.newValue || '';
config = message.config; if (changes.uuid) config.uuid = changes.uuid.newValue || '';
if (changes.enabled !== undefined) config.enabled = changes.enabled.newValue !== false;
if (changes.cloneAccel !== undefined) config.cloneAccel = changes.cloneAccel.newValue !== false;
if (changes.downloadAccel !== undefined) config.downloadAccel = changes.downloadAccel.newValue !== false;
if (config.enabled) {
scanDownloadLinks(); scanDownloadLinks();
processCloneUrl();
} }
}); });
// 防抖变量
let scanTimeout = null;
let lastScanTime = 0;
const SCAN_THROTTLE = 500;
// 节流扫描函数
function throttledScan() {
const now = Date.now();
if (now - lastScanTime < SCAN_THROTTLE) {
if (scanTimeout) clearTimeout(scanTimeout);
scanTimeout = setTimeout(() => {
lastScanTime = Date.now();
scanDownloadLinks();
processCloneUrl();
}, SCAN_THROTTLE);
return;
}
lastScanTime = now;
scanDownloadLinks();
processCloneUrl();
}
// 监听 DOM 变化 // 监听 DOM 变化
const observer = new MutationObserver((mutations) => { const observer = new MutationObserver((mutations) => {
let shouldScan = false; let shouldScan = false;
@@ -159,7 +312,7 @@
}); });
if (shouldScan) { if (shouldScan) {
setTimeout(scanDownloadLinks, 100); throttledScan();
} }
}); });
@@ -167,23 +320,26 @@
async function init() { async function init() {
await loadConfig(); await loadConfig();
scanDownloadLinks(); scanDownloadLinks();
processCloneUrl();
// 监听 DOM 变化
observer.observe(document.body, { observer.observe(document.body, {
childList: true, childList: true,
subtree: true subtree: true
}); });
// 页面导航时重新扫描GitHub 使用 pjax document.addEventListener('pjax:end', () => {
document.addEventListener('pjax:end', scanDownloadLinks); scanDownloadLinks();
document.addEventListener('turbo:load', scanDownloadLinks); processCloneUrl();
});
document.addEventListener('turbo:load', () => {
scanDownloadLinks();
processCloneUrl();
});
} }
// 页面加载完成后初始化
if (document.readyState === 'loading') { if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init); document.addEventListener('DOMContentLoaded', init);
} else { } else {
init(); init();
} }
})(); })();

View File

@@ -1,10 +1,12 @@
// 加载配置 // 加载配置
document.addEventListener('DOMContentLoaded', async () => { document.addEventListener('DOMContentLoaded', async () => {
const config = await chrome.storage.sync.get(['workersUrl', 'uuid', 'enabled']); const config = await chrome.storage.sync.get(['workersUrl', 'uuid', 'enabled', 'cloneAccel', 'downloadAccel']);
document.getElementById('workersUrl').value = config.workersUrl || ''; document.getElementById('workersUrl').value = config.workersUrl || '';
document.getElementById('uuid').value = config.uuid || ''; document.getElementById('uuid').value = config.uuid || '';
document.getElementById('enabled').checked = config.enabled !== false; document.getElementById('enabled').checked = config.enabled !== false;
document.getElementById('cloneAccel').checked = config.cloneAccel !== false;
document.getElementById('downloadAccel').checked = config.downloadAccel !== false;
}); });
// 保存配置 // 保存配置
@@ -12,32 +14,30 @@ document.getElementById('saveBtn').addEventListener('click', async () => {
const workersUrl = document.getElementById('workersUrl').value.trim(); const workersUrl = document.getElementById('workersUrl').value.trim();
const uuid = document.getElementById('uuid').value.trim(); const uuid = document.getElementById('uuid').value.trim();
const enabled = document.getElementById('enabled').checked; const enabled = document.getElementById('enabled').checked;
const cloneAccel = document.getElementById('cloneAccel').checked;
const downloadAccel = document.getElementById('downloadAccel').checked;
const statusEl = document.getElementById('status'); const statusEl = document.getElementById('status');
// 验证 // 验证 - Workers 地址必填
if (!workersUrl) { if (!workersUrl) {
statusEl.textContent = '请填写 Workers 地址'; statusEl.textContent = '请填写 Workers 地址';
statusEl.className = 'status error'; statusEl.className = 'status error';
return; return;
} }
if (!uuid) { // UUID 格式验证(如果填写了)
statusEl.textContent = '请填写 UUID'; if (uuid) {
statusEl.className = 'status error'; const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
return; if (!uuidRegex.test(uuid)) {
} statusEl.textContent = 'UUID 格式不正确';
statusEl.className = 'status error';
// UUID 格式验证 return;
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; }
if (!uuidRegex.test(uuid)) {
statusEl.textContent = 'UUID 格式不正确';
statusEl.className = 'status error';
return;
} }
// 保存 // 保存
await chrome.storage.sync.set({ workersUrl, uuid, enabled }); await chrome.storage.sync.set({ workersUrl, uuid, enabled, cloneAccel, downloadAccel });
statusEl.textContent = '配置已保存'; statusEl.textContent = '配置已保存';
statusEl.className = 'status success'; statusEl.className = 'status success';
@@ -45,11 +45,13 @@ document.getElementById('saveBtn').addEventListener('click', async () => {
// 通知 content script 更新配置 // 通知 content script 更新配置
const tabs = await chrome.tabs.query({ active: true, currentWindow: true }); const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
if (tabs[0] && tabs[0].url.includes('github.com')) { if (tabs[0] && tabs[0].url.includes('github.com')) {
chrome.tabs.sendMessage(tabs[0].id, { type: 'CONFIG_UPDATED', config: { workersUrl, uuid, enabled } }); chrome.tabs.sendMessage(tabs[0].id, {
type: 'CONFIG_UPDATED',
config: { workersUrl, uuid, enabled, cloneAccel, downloadAccel }
});
} }
setTimeout(() => { setTimeout(() => {
statusEl.className = 'status'; statusEl.className = 'status';
}, 2000); }, 2000);
}); });