Git仓库一键跳转HPX

在Git仓库页面添加跳转到HPX打包页面的按钮

// ==UserScript==
// @name         Git仓库一键跳转HPX
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  在Git仓库页面添加跳转到HPX打包页面的按钮
// @author       Dean
// @match        https://dev.sankuai.com/code/repo-detail/*
// @grant        GM_xmlhttpRequest
// @require      https://code.jquery.com/jquery-3.6.0.min.js
// ==/UserScript==

(function() {
    'use strict';

    // 添加样式
    const style = document.createElement('style');
    style.textContent = `
        #zy_hpx_button {
            margin-right: 8px;
            position: relative;
            overflow: hidden;
        }
        .mtd-button-content {
            display: flex;
            align-items: center;
            justify-content: center;
            position: relative;
            z-index: 2;
        }
        .mtdicon-fast-forward {
            margin-right: 4px;
        }

        /* 节日装饰样式 */
        .festival-icon {
            position: absolute;
            pointer-events: none;
            font-size: 12px;
            z-index: 1;
        }

        /* 春节样式 */
        .spring-festival .festival-icon {
            animation: springFestival 2s infinite;
        }

        /* 圣诞节样式 */
        .christmas .festival-icon {
            animation: snowfall 3s infinite;
        }

        /* 万圣节样式 */
        .halloween .festival-icon {
            animation: spooky 3s infinite;
        }

        /* 元宵节样式 */
        .lantern-festival .festival-icon {
            animation: floating 3s infinite;
        }

        /* 动画效果 */
        @keyframes springFestival {
            0% { transform: scale(1) rotate(0deg); opacity: 1; }
            50% { transform: scale(1.2) rotate(180deg); opacity: 0.8; }
            100% { transform: scale(1) rotate(360deg); opacity: 1; }
        }

        @keyframes snowfall {
            0% { transform: translateY(-100%) rotate(0deg); opacity: 1; }
            100% { transform: translateY(100%) rotate(360deg); opacity: 0; }
        }

        @keyframes spooky {
            0% { transform: translateX(-20px) translateY(0); opacity: 1; }
            50% { transform: translateX(20px) translateY(-10px); opacity: 0.7; }
            100% { transform: translateX(-20px) translateY(0); opacity: 1; }
        }

        @keyframes floating {
            0% { transform: translateY(0) rotate(-5deg); }
            50% { transform: translateY(-10px) rotate(5deg); }
            100% { transform: translateY(0) rotate(-5deg); }
        }

        /* 光效装饰 */
        .festival-sparkle {
            position: absolute;
            width: 100%;
            height: 100%;
            top: 0;
            left: 0;
            pointer-events: none;
            z-index: 1;
        }
        .festival-sparkle::before,
        .festival-sparkle::after {
            content: '';
            position: absolute;
            width: 2px;
            height: 2px;
            border-radius: 50%;
            background: rgba(255,255,255,0.6);
            animation: sparkle 2s infinite;
        }
        .festival-sparkle::after {
            animation-delay: 1s;
        }

        @keyframes sparkle {
            0%, 100% { transform: translate(0, 0) scale(0); opacity: 0; }
            50% { transform: translate(20px, -20px) scale(1); opacity: 1; }
        }
    `;
    document.head.appendChild(style);

    // 页面加载完成后执行
    if (window.location.toString().indexOf('dev.sankuai.com/code/repo-detail') >= 0) {
        // 使用 MutationObserver 监听DOM变化
        const observer = new MutationObserver((mutations, observer) => {
            if ($(".btn-box").length > 0 && $("#zy_hpx_button").length === 0) {
                logger('检测到按钮容器');
                observer.disconnect(); // 停止观察
                inject(() => {});
            }
        });

        // 立即检查是否已存在按钮容器
        if ($(".btn-box").length > 0) {
            logger('按钮容器已存在');
            inject(() => {});
        } else {
            logger('等待按钮容器');
            // 开始观察
            observer.observe(document.body, {
                childList: true,
                subtree: true
            });
        }

        // 添加页面 URL 变化监听
        let lastUrl = location.href;
        new MutationObserver(() => {
            const url = location.href;
            if (url !== lastUrl) {
                lastUrl = url;
                logger('URL 发生变化');
                if (url.indexOf('dev.sankuai.com/code/repo-detail') >= 0) {
                    inject(() => {});
                }
            }
        }).observe(document, {subtree: true, childList: true});
    }

    // 缓存键名
    const CACHE_KEY = 'HPX_PROJECT_CACHE';
    const CACHE_EXPIRE = 24 * 60 * 60 * 1000; // 24小时缓存

    // 获取缓存的项目数据
    function getCachedProject(git) {
        try {
            const cache = JSON.parse(localStorage.getItem(CACHE_KEY) || '{}');
            const data = cache[git];
            if (data && (Date.now() - data.timestamp) < CACHE_EXPIRE) {
                return data.project;
            }
        } catch (e) {
            logger('读取缓存失败', e);
        }
        return null;
    }

    // 设置项目缓存
    function setCachedProject(git, project) {
        try {
            const cache = JSON.parse(localStorage.getItem(CACHE_KEY) || '{}');
            cache[git] = {
                project: project,
                timestamp: Date.now()
            };
            localStorage.setItem(CACHE_KEY, JSON.stringify(cache));
        } catch (e) {
            logger('设置缓存失败', e);
        }
    }

    // 入侵
    function inject(callback) {
        if ($(".btn-box").length <= 0) {
            logger('没有查到元素');
            return false;
        }
        logger('查到元素');

        // 先渲染一个加载中的按钮
        renderLoadingButton();

        // 查询git地址
        getGitAddress(function(git) {
            if (git.length <= 0) {
                removeButton();
                callback(true);
                return;
            }

            // 先检查缓存
            const cachedProject = getCachedProject(git);
            if (cachedProject) {
                logger('使用缓存数据');
                renderHPXButton(cachedProject);
                callback(true);

                // 异步更新缓存
                updateProjectCache(git);
                return;
            }

            // 无缓存时请求新数据
            requestProjectData(git, callback);
        });
    }

    // 异步更新缓存
    function updateProjectCache(git) {
        GM_xmlhttpRequest({
            method: 'GET',
            url: 'https://hpx.sankuai.com/api/open/getProjectUrlList?repoUrl=' + git,
            onload: function(response) {
                try {
                    const data = JSON.parse(response.responseText);
                    if (data.data && data.data.length > 0) {
                        const project = data.data[data.data.length - 1];
                        setCachedProject(git, project);
                        logger('缓存已更新');
                    }
                } catch (e) {
                    logger('更新缓存失败', e);
                }
            }
        });
    }

    // 请求项目数据
    function requestProjectData(git, callback) {
        GM_xmlhttpRequest({
            method: 'GET',
            url: 'https://hpx.sankuai.com/api/open/getProjectUrlList?repoUrl=' + git,
            onload: function(response) {
                try {
                    const data = JSON.parse(response.responseText);
                    if (data.data && data.data.length > 0) {
                        const project = data.data[data.data.length - 1];
                        if (project.length > 0) {
                            logger('获取新数据');
                            setCachedProject(git, project);
                            renderHPXButton(project);
                            callback(true);
                            return;
                        }
                    }
                    // 如果没有获取到有效数据,移除loading按钮
                    removeButton();
                    callback(true);
                } catch (e) {
                    logger('请求数据失败', e);
                    removeButton();
                    callback(true);
                }
            },
            onerror: function() {
                logger('网络请求失败');
                removeButton();
                callback(true);
            }
        });
    }

    // 渲染加载中按钮
    function renderLoadingButton() {
        removeButton(); // 先移除已存在的按钮
        $(".btn-box").prepend(`
            <button id="zy_hpx_button" type="button" class="mtd-btn mtd-btn-primary">
                <span>
                    <div class="mtd-button-content">
                        <span class="mtdicon mtdicon-fast-forward"></span>
                        <span>Loading...</span>
                    </div>
                </span>
            </button>
        `);
    }

    // 渲染按钮
    function renderHPXButton(project) {
        removeButton(); // 先移除已存在的按钮
        const festival = getFestival();
        const festivalConfig = {
            'spring-festival': {
                icon: '🏮',
                text: '新年快乐',
                icons: ['🏮', '💰', '🧨', '🎊', '🐲', '福']
            },
            'lantern-festival': {
                icon: '🏮',
                text: '元宵节快乐',
                icons: ['🏮', '👻', '🌕', '⭐']
            },
            'halloween': {
                icon: '🎃',
                text: 'Happy Halloween',
                icons: ['🎃', '👻', '🦇', '🕷️', '🕸️']
            },
            'christmas': {
                icon: '🎄',
                text: 'Merry Xmas',
                icons: ['❄️', '🎄', '🎅', '🎁', '⛄', '🦌']
            }
        };

        // 在首部插入Button
        $(".btn-box").prepend(`
            <button id="zy_hpx_button" type="button" class="mtd-btn mtd-btn-primary ${festival}">
                ${festival ? '<div class="festival-sparkle"></div>' : ''}
                <span>
                    <div class="mtd-button-content">
                        <span class="mtdicon mtdicon-fast-forward"></span>
                        <span>Go to HyperloopX</span>
                        ${festival ? `<span style="margin-left: 4px">${festivalConfig[festival].icon}</span>` : ''}
                    </div>
                </span>
            </button>
        `);

        $("#zy_hpx_button").click(function(){
            // 点击效果
            if (festival) {
                const config = festivalConfig[festival];
                const icon = config.icons[Math.floor(Math.random() * config.icons.length)];
                const $icon = $(`<span class="festival-icon">${icon}</span>`);
                $icon.css({
                    left: '50%',
                    top: '50%',
                    transform: 'translate(-50%, -50%) scale(3)',
                    opacity: 0
                });
                $(this).append($icon);
                setTimeout(() => $icon.remove(), 500);
            }

            // 打开窗口
            window.open(project);
        });
    }

    // 统一的按钮移除函数
    function removeButton() {
        $("#zy_hpx_button").remove();
    }

    // 查询git地址
    function getGitAddress(callback) {
        var str = 'dev.sankuai.com/code/repo-detail';
        var index = window.location.toString().indexOf(str);
        var reset = window.location.toString().substring(index + str.length);
        var components = reset.split('/');

        if (components.length >= 3) {
            var url = 'https://dev.sankuai.com/rest/api/2.0/projects/' + components[1] + '/repos/' + components[2];
            $.get(url, {}, function(data){
                var git = '';
                for (let i = 0; i < data.links.clone.length; i++) {
                    let item = data.links.clone[i];
                    if (item.name === 'ssh') {
                        git = item.href;
                        break;
                    }
                }
                callback(git);
            });
        }
        return '';
    }

    // 获取当前节日
    function getFestival() {
        const date = new Date();
        const month = date.getMonth() + 1;
        const day = date.getDate();

        // 农历新年判断(这里使用简化判断,实际应该使用农历计算)
        if (month === 1 && day >= 20 || month === 2 && day <= 20) {
            return 'spring-festival';
        }

        // 元宵节
        if (month === 2 && day >= 24 && day <= 26) {
            return 'lantern-festival';
        }

        // 万圣节
        if (month === 10 && day >= 29 || month === 11 && day <= 2) {
            return 'halloween';
        }

        // 圣诞节
        if (month === 12 && day >= 20 && day <= 26) {
            return 'christmas';
        }

        return '';
    }

    // log
    function logger(log) {
        console.log("[go to HPX]", log);
    }
})();