GitHub Proxy Redirector | GitHub 代理自动选择

自动代理GitHub链接,支持自定义代理服务器和域名

// ==UserScript==
// @name         GitHub Proxy Redirector | GitHub 代理自动选择
// @namespace    https://github.com/DingerBtn/GithubAutoProxy
// @version      2.0
// @description  自动代理GitHub链接,支持自定义代理服务器和域名
// @author       DingerBtn
// @homepage     https://github.com/DingerBtn/GithubAutoProxy
// @license      GPL-3.0
// @match        *://github.com/*
// @match        *://gist.github.com/*
// @match        *://raw.githubusercontent.com/*
// @match        *://*.github.io/*
// @match        *://githubassets.com/*
// @match        *://github.dev/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @grant        GM_addStyle
// @run-at       document-start
// @icon         https://github.githubassets.com/favicons/favicon.png
// @thanks       https://github.akams.cn/
// ==/UserScript==



(function() {
    'use strict';

    // 只在 github 相关域名下启用脚本
    const GITHUB_DOMAINS = [
        'github.com',
        'gist.github.com',
        'raw.githubusercontent.com',
        'github.io',
        'githubassets.com',
        'github.dev'
    ];
    const isGithubSite = GITHUB_DOMAINS.some(domain => {
        const host = window.location.hostname.toLowerCase();
        return host === domain || host.endsWith('.' + domain);
    });
    if (!isGithubSite) return;

    // ========== 默认配置 ==========
    const DEFAULT_DOMAINS = [
        "github.com",
        "gist.github.com",
        "raw.githubusercontent.com",
        "github.io",
        "githubassets.com",
        "github.dev"
    ];
    const DEFAULT_CONFIG = {
        enabled: true,
        proxyList: [
            { id: 1, name: "默认代理", prefix: "https://github.akams.cn/", active: true, domains: [...DEFAULT_DOMAINS] }
        ]
    };

    // ========== 配置管理 ==========
    let config = GM_getValue('proxyConfig', DEFAULT_CONFIG);

    // 保证配置结构完整
    if (!config.proxyList) config.proxyList = DEFAULT_CONFIG.proxyList;
    if (typeof config.enabled !== 'boolean') config.enabled = DEFAULT_CONFIG.enabled;
    // 兼容老配置
    if (config.proxyList && config.proxyList.length > 0 && !config.proxyList[0].domains) {
        const oldDomains = config.targetDomains || DEFAULT_DOMAINS;
        config.proxyList.forEach(p => p.domains = [...oldDomains]);
        delete config.targetDomains;
    }

    const saveConfig = () => GM_setValue('proxyConfig', config);
    const getActiveProxy = () => config.proxyList.find(p => p.active) || config.proxyList[0];

    // ========== 代理核心 ==========
    // 只代理特定类型的 GitHub 链接
    const shouldProxy = (url) => {
        if (!config.enabled) return false;
        if (!url) return false;
        try {
            const u = new URL(url);
            const host = u.hostname.toLowerCase();
            const path = u.pathname;
            // 1. 分支源码
            if (host === 'github.com' &&
                (/\/archive\/(master\.zip|main\.zip|[\w.-]+\.(zip|tar\.gz))$/.test(path))) {
                return true;
            }
            // 2. release源码
            if (host === 'github.com' &&
                /\/archive\/(v[\w.-]+\.(zip|tar\.gz))$/.test(path)) {
                return true;
            }
            // 3. release文件
            if (host === 'github.com' &&
                /\/releases\/download\//.test(path)) {
                return true;
            }
            // 4. commit文件
            if (host === 'github.com' &&
                /\/blob\/[\w\d]+\//.test(path)) {
                return true;
            }
            // 5. gist
            if (host === 'gist.githubusercontent.com' &&
                /\/raw\//.test(path)) {
                return true;
            }
            // 6. api
            if (host === 'api.github.com') {
                return true;
            }
            return false;
        } catch {
            return false;
        }
    };

    const proxyUrl = (original) => {
        const proxyPrefix = getActiveProxy().prefix;
        if (original.startsWith(proxyPrefix)) return original;
        return proxyPrefix + original;
    };

    // ========== 代理逻辑 ==========
    // 1. 拦截a标签点击
    document.addEventListener('click', event => {
        const link = event.target.closest && event.target.closest('a');
        if (!link || !shouldProxy(link.href)) return;
        // 忽略新窗口/标签页
        if (event.ctrlKey || event.shiftKey || event.metaKey || event.button !== 0) return;
        event.preventDefault();
        window.location.href = proxyUrl(link.href);
    }, true);

    // 2. 拦截AJAX请求
    const originalOpen = XMLHttpRequest.prototype.open;
    XMLHttpRequest.prototype.open = function(method, url) {
        if (shouldProxy(url)) {
            arguments[1] = proxyUrl(url);
        }
        return originalOpen.apply(this, arguments);
    };

    // 3. 处理动态内容
    const processLinks = () => {
        document.querySelectorAll('a[href]').forEach(link => {
            if (shouldProxy(link.href) && !link.dataset.proxied) {
                link.dataset.proxied = true;
                // 防止右键菜单直接跳转
                link.addEventListener('click', e => {
                    if (e.ctrlKey || e.shiftKey || e.metaKey || e.button !== 0) return;
                    e.preventDefault();
                    window.location.href = proxyUrl(link.href);
                });
            }
        });
    };

    // 4. 监听DOM变化
    const observer = new MutationObserver(processLinks);
    window.addEventListener('DOMContentLoaded', () => {
        observer.observe(document.body, {
            childList: true,
            subtree: true,
            attributes: true,
            attributeFilter: ['href']
        });
        processLinks();
    });

    // 5. 处理页面初始加载
    window.addEventListener('load', processLinks);

    // ========== 设置界面 ========== 
    function createSettingsUI() {
        GM_addStyle(`
            /* 脚本名称样式 */
            .settings-title { font-size: 18px; font-weight: 600; }
            .manage-domains-btn {
                background: #0366d6;
                color: #fff;
                border: none;
                border-radius: 4px;
                padding: 6px 14px;
                font-size: 14px;
                margin-left: 14px;
                cursor: pointer;
                transition: background 0.2s;
            }
            .manage-domains-btn:hover {
                background: #024fa2;
            }
            .proxy-domains {
                display: flex;
                flex-wrap: wrap;
                gap: 6px;
                margin: 6px 0 0 0;
            }
            .proxy-domain-tag {
                background: #f1f8ff;
                border: 1px solid #c8e1ff;
                border-radius: 4px;
                padding: 2px 7px;
                font-size: 12px;
                display: flex;
                align-items: center;
            }
            .remove-proxy-domain {
                margin-left: 4px;
                cursor: pointer;
                color: #cb2431;
            }
            .add-proxy-domain {
                display: flex;
                margin-top: 4px;
            }
            .add-proxy-domain-input {
                flex: 1;
                padding: 4px;
                border: 1px solid #ddd;
                border-radius: 4px;
                font-size: 13px;
            }
            .add-proxy-domain-btn {
                background: #28a745;
                color: white;
                border: none;
                border-radius: 4px;
                padding: 4px 10px;
                margin-left: 6px;
                cursor: pointer;
            }
            .proxy-settings {
                position: fixed;
                top: 20px;
                right: 20px;
                z-index: 9999;
                background: white;
                border: 1px solid #ddd;
                border-radius: 8px;
                padding: 20px;
                width: 400px;
                box-shadow: 0 4px 12px rgba(0,0,0,0.15);
                font-family: system-ui, sans-serif;
                color: #333;
            }
            .settings-header {
                display: flex;
                justify-content: space-between;
                align-items: center;
                margin-bottom: 15px;
                padding-bottom: 10px;
                border-bottom: 1px solid #eee;
            }
            .settings-title {
                font-size: 18px;
                font-weight: 600;
            }
            .close-btn {
                background: none;
                border: none;
                font-size: 20px;
                cursor: pointer;
                color: #666;
            }
            .section-title {
                margin: 15px 0 8px 0;
                font-weight: 500;
                color: #0366d6;
                font-size: 15px;
            }
            .proxy-list {
                margin: 10px 0;
            }
            .proxy-item {
                display: flex;
                align-items: center;
                padding: 8px;
                border: 1px solid #eee;
                border-radius: 4px;
                margin-bottom: 8px;
            }
            .proxy-radio {
                margin-right: 10px;
            }
            .proxy-input {
                flex: 1;
                padding: 6px;
                border: 1px solid #ddd;
                border-radius: 4px;
                font-size: 14px;
            }
            .domain-list {
                display: flex;
                flex-wrap: wrap;
                gap: 8px;
                margin: 10px 0;
            }
            .domain-tag {
                background: #f1f8ff;
                border: 1px solid #c8e1ff;
                border-radius: 4px;
                padding: 4px 8px;
                font-size: 13px;
                display: flex;
                align-items: center;
            }
            .remove-domain {
                margin-left: 6px;
                cursor: pointer;
                color: #cb2431;
            }
            .add-domain {
                display: flex;
                margin-top: 10px;
            }
            .domain-input {
                flex: 1;
                padding: 6px;
                border: 1px solid #ddd;
                border-radius: 4px;
                font-size: 14px;
            }
            .add-btn {
                background: #28a745;
                color: white;
                border: none;
                border-radius: 4px;
                padding: 6px 12px;
                margin-left: 8px;
                cursor: pointer;
            }
            .save-btn {
                background: #0366d6;
                color: white;
                border: none;
                border-radius: 4px;
                padding: 8px 16px;
                width: 100%;
                margin-top: 15px;
                font-weight: 500;
                cursor: pointer;
            }
        `);

        const settingsPanel = document.createElement('div');
        settingsPanel.className = 'proxy-settings';
        settingsPanel.innerHTML = `
            <div class="settings-header">
                <div class="settings-title">GitHub 代理选择</div>
                <button class="close-btn">×</button>
            </div>
            <div style="margin-bottom: 10px;">
                <label style="display: flex; align-items: center; gap: 8px; font-size: 15px;">
                    <input type="checkbox" id="proxyEnabled" ${config.enabled ? 'checked' : ''}>
                    <span>启用代理</span>
                </label>
            </div>
            <div class="section-title">代理服务器</div>
            <div class="proxy-list" id="proxyList"></div>
            <div class="section-title">目标域名</div>
            <div class="domain-list" id="domainList"></div>
            <div class="add-domain">
                <input type="text" class="domain-input" id="newDomain" placeholder="添加新域名 (如: github.com)">
                <button class="add-btn" id="addDomainBtn">+ 添加</button>
            </div>
            <button class="save-btn" id="saveConfigBtn">保存设置</button>
        `;
        document.body.appendChild(settingsPanel);

        // 关闭按钮
        settingsPanel.querySelector('.close-btn').addEventListener('click', () => {
            document.body.removeChild(settingsPanel);
        });

        // 渲染代理服务器列表
        const renderProxyList = () => {
            const container = settingsPanel.querySelector('#proxyList');
            container.innerHTML = '';
            config.proxyList.forEach(proxy => {
                const proxyItem = document.createElement('div');
                proxyItem.className = 'proxy-item';
                proxyItem.innerHTML = `
                    <input type="radio" name="activeProxy" class="proxy-radio"
                           ${proxy.active ? 'checked' : ''} data-id="${proxy.id}">
                    <input type="text" class="proxy-input" value="${proxy.prefix}"
                           data-id="${proxy.id}" placeholder="https://代理地址/">
                    <button class="manage-domains-btn" data-id="${proxy.id}">管理域名</button>
                `;
                container.appendChild(proxyItem);
            });
        };

        // 域名管理弹窗
        function openDomainManager(proxyId) {
            const proxy = config.proxyList.find(p => p.id === proxyId);
            if (!proxy) return;
            let modal = document.createElement('div');
            modal.style = 'position:fixed;top:0;left:0;width:100vw;height:100vh;background:rgba(0,0,0,0.25);z-index:10000;display:flex;align-items:center;justify-content:center;';
            modal.innerHTML = `
                <div style="background:#fff;padding:24px 28px;border-radius:10px;min-width:340px;max-width:90vw;box-shadow:0 4px 16px rgba(0,0,0,0.18);">
                    <div style="font-size:17px;font-weight:600;margin-bottom:12px;">管理代理域名</div>
                    <div id="domain-list-modal" style="display:flex;flex-wrap:wrap;gap:8px;margin-bottom:12px;"></div>
                    <div style="display:flex;gap:8px;">
                        <input id="add-domain-modal-input" type="text" placeholder="添加域名 (如: github.com)" style="flex:1;padding:6px;border:1px solid #ddd;border-radius:4px;font-size:14px;">
                        <button id="add-domain-modal-btn" style="background:#28a745;color:#fff;border:none;border-radius:4px;padding:6px 14px;cursor:pointer;">+ 添加</button>
                    </div>
                    <div style="text-align:right;margin-top:18px;">
                        <button id="close-domain-modal-btn" style="background:#0366d6;color:#fff;border:none;border-radius:4px;padding:7px 18px;font-size:15px;cursor:pointer;">完成</button>
                    </div>
                </div>
            `;
            document.body.appendChild(modal);
            const renderModalList = () => {
                const list = modal.querySelector('#domain-list-modal');
                list.innerHTML = '';
                proxy.domains.forEach(domain => {
                    const tag = document.createElement('div');
                    tag.style = 'background:#f1f8ff;border:1px solid #c8e1ff;border-radius:4px;padding:4px 10px;font-size:13px;display:flex;align-items:center;';
                    tag.innerHTML = `${domain}<span data-domain="${domain}" style="margin-left:6px;cursor:pointer;color:#cb2431;font-size:15px;">×</span>`;
                    tag.querySelector('span').onclick = () => {
                        proxy.domains = proxy.domains.filter(d => d !== domain);
                        renderModalList();
                        renderProxyDomainsSelect(proxy);
                    };
                    list.appendChild(tag);
                });
            };
            renderModalList();
            modal.querySelector('#add-domain-modal-btn').onclick = () => {
                const input = modal.querySelector('#add-domain-modal-input');
                const domain = input.value.trim().toLowerCase();
                if (domain && !proxy.domains.includes(domain)) {
                    proxy.domains.push(domain);
                    input.value = '';
                    renderModalList();
                    renderProxyDomainsSelect(proxy);
                }
            };
            modal.querySelector('#close-domain-modal-btn').onclick = () => {
                document.body.removeChild(modal);
            };
        }

        // 渲染域名列表
        const renderDomainList = () => {
            const container = settingsPanel.querySelector('#domainList');
            container.innerHTML = '';
            config.targetDomains.forEach(domain => {
                const domainTag = document.createElement('div');
                domainTag.className = 'domain-tag';
                domainTag.innerHTML = `
                    ${domain}
                    <span class="remove-domain" data-domain="${domain}">×</span>
                `;
                container.appendChild(domainTag);
            });
        };

        // 初始渲染
        renderProxyList();

        // 代理服务器单选、域名选择、管理域名按钮
        settingsPanel.addEventListener('change', e => {
            if (e.target.classList.contains('proxy-radio')) {
                const id = parseInt(e.target.dataset.id);
                config.proxyList.forEach(p => p.active = (p.id === id));
            }
            if (e.target.classList.contains('proxy-domain-checkbox')) {
                const id = parseInt(e.target.dataset.id);
                const domain = e.target.dataset.domain;
                const proxy = config.proxyList.find(p => p.id === id);
                if (!proxy) return;
                if (e.target.checked) {
                    if (!proxy.domains.includes(domain)) proxy.domains.push(domain);
                } else {
                    proxy.domains = proxy.domains.filter(d => d !== domain);
                }
            }
        });
        settingsPanel.addEventListener('click', e => {
            if (e.target.classList.contains('manage-domains-btn')) {
                const id = parseInt(e.target.dataset.id);
                openDomainManager(id);
            }
        });

        // 兼容保留原有域名管理(可选:如不需要可移除)
        // settingsPanel.querySelector('#addDomainBtn').addEventListener('click', ...)
        // settingsPanel.addEventListener('click', ...)

        // 启用/禁用代理
        settingsPanel.querySelector('#proxyEnabled').addEventListener('change', e => {
            config.enabled = e.target.checked;
        });

        // 保存配置
        settingsPanel.querySelector('#saveConfigBtn').addEventListener('click', () => {
            saveConfig();
            alert('设置已保存!页面将刷新应用更改');
            location.reload();
        });
    }

    // ========== 菜单与按钮 ==========
    GM_registerMenuCommand("GitHub 代理设置", createSettingsUI);

    // 页面右下角设置按钮
    function createSettingsButton() {
        const btn = document.createElement('button');
        btn.innerHTML = config.enabled ? '⚙️' : '⚙️❌';
        btn.title = config.enabled ? 'GitHub 代理已启用' : 'GitHub 代理已禁用';
        btn.style = `
            position: fixed;
            bottom: 20px;
            right: 20px;
            z-index: 9999;
            width: 40px;
            height: 40px;
            border-radius: 50%;
            background: #0366d6;
            color: white;
            border: none;
            font-size: 20px;
            cursor: pointer;
            box-shadow: 0 2px 10px rgba(0,0,0,0.2);
        `;
        btn.addEventListener('click', createSettingsUI);
        document.body.appendChild(btn);
    }
    // 仅在 github 相关域名下插入设置按钮
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', createSettingsButton);
    } else {
        createSettingsButton();
    }
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址