Greasy Fork

来自缓存

Greasy Fork is available in English.

GitHub Proxy Redirector | GitHub 代理自动选择

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

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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();
    }
})();