GitHub镜像站增强工具

汉化GitHub镜像站界面、加速下载、自动跳转到镜像站

// ==UserScript==
// @name         GitHub镜像站增强工具
// @namespace    https://github.com/GamblerIX/
// @description  汉化GitHub镜像站界面、加速下载、自动跳转到镜像站
// @icon         https://github.githubassets.com/pinned-octocat.svg
// @version      3.0.3
// @author       GamblerIX
// @license      MIT
// @homepage     https://github.com/GamblerIX/GithubProxy
// @supportURL   https://github.com/GamblerIX/GithubProxy/issues
// @match        https://github.com/*
// @match        https://hub.mihoyo.online/*
// @match        https://skills.github.com/*
// @match        https://gist.github.com/*
// @match        https://gist.mihoyo.online/*
// @match        https://education.github.com/*
// @match        https://www.githubstatus.com/*
// @require      https://update.greasyfork.org/scripts/461072/1661491/GitHub%20%E4%B8%AD%E6%96%87.js
// @run-at       document-start
// @grant        GM_xmlhttpRequest
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @grant        GM_notification
// @grant        GM_setClipboard
// @grant        GM_openInTab
// @grant        window.onurlchange
// @connect      fanyi.iflyrec.com
// @noframes
// ==/UserScript==

(function () {
    'use strict';

    // ==================== 配置常量 ====================
    const CONFIG = {
        VERSION: '3.0.3',
        LANG: 'zh-CN',

        // 存储键名
        STORAGE_KEYS: {
            AUTO_REDIRECT: 'auto_redirect',
            ENABLE_TRANSLATION: 'enable_translation',
            RAW_FAST_INDEX: 'xiu2_menu_raw_fast',
            RAW_DOWN_LINK: 'menu_rawDownLink',
            GIT_CLONE: 'menu_gitClone',
        },

        // 性能配置
        PERFORMANCE: {
            MAX_TEXT_LENGTH: 500,
            DEBOUNCE_DELAY: 300,
            CACHE_EXPIRE_TIME: 5 * 60 * 1000,
            REQUEST_TIMEOUT: 10000,
        },

        // CSS选择器
        SELECTORS: {
            RELEASE_FOOTER: '.Box-footer',
            RAW_BUTTON: 'a[data-testid="raw-button"]',
            FILE_ICONS: 'div.Box-row svg.octicon.octicon-file, .react-directory-filename-column>svg.color-fg-muted',
        },

        // CSS类名
        CSS_CLASSES: {
            XIU2_RS: 'XIU2-RS',
            XIU2_RF: 'XIU2-RF',
            FILE_DOWNLOAD_LINK: 'fileDownLink',
            TRANSLATE_BUTTON: 'translate-me',
        }
    };

    // ==================== 下载源配置 ====================
    const DOWNLOAD_SOURCES = {
        release: [
            ['https://releases.mihoyo.online/https://github.com', '自建', 'CF CDN - 自建加速源'],
            ['https://github.com', '官方', 'GitHub 官方源']
        ],
        clone: [
            ['https://gitclone.com', 'GitClone', '大陆推荐,首次慢,缓存后较快'],
            ['https://github.com', '官方', 'GitHub 官方源']
        ],
        ssh: [
            ['ssh://[email protected]:443/', '官方SSH', 'GitHub 官方 SSH 443端口']
        ],
        raw: [
            ['https://raw.mihoyo.online', '自建', 'CF CDN - 自建加速源'],
            ['https://raw.githubusercontent.com', '官方', 'GitHub 官方源']
        ]
    };

    // ==================== 工具函数 ====================
    const Utils = {
        // 防抖函数
        debounce(func, wait) {
            let timeout;
            return function executedFunction(...args) {
                const later = () => {
                    clearTimeout(timeout);
                    func(...args);
                };
                clearTimeout(timeout);
                timeout = setTimeout(later, wait);
            };
        },

        // 安全DOM查询
        safeQuerySelector(selector, parent = document) {
            try {
                return parent.querySelector(selector);
            } catch (error) {
                console.warn(`[GitHub增强] 查询选择器失败: ${selector}`, error);
                return null;
            }
        },

        // 安全DOM查询(多个)
        safeQuerySelectorAll(selector, parent = document) {
            try {
                return parent.querySelectorAll(selector);
            } catch (error) {
                console.warn(`[GitHub增强] 查询选择器失败: ${selector}`, error);
                return [];
            }
        },

        // 创建元素
        createElement(tag, attributes = {}, textContent = '') {
            const element = document.createElement(tag);
            Object.entries(attributes).forEach(([key, value]) => {
                if (key === 'style' && typeof value === 'object') {
                    Object.assign(element.style, value);
                } else {
                    element.setAttribute(key, value);
                }
            });
            if (textContent) element.textContent = textContent;
            return element;
        },

        // 获取嵌套属性
        getNestedProperty(obj, path) {
            return path.split('.').reduce((acc, part) => {
                const match = part.match(/(\w+)(?:\[(\d+)\])?/);
                if (!match) return undefined;
                const key = match[1];
                const index = match[2];
                if (acc && acc[key] !== undefined) {
                    return index !== undefined ? acc[key][index] : acc[key];
                }
                return undefined;
            }, obj);
        }
    };

    // ==================== 存储管理 ====================
    const Storage = {
        get(key, defaultValue = null) {
            try {
                return GM_getValue(key, defaultValue);
            } catch (error) {
                console.warn(`[GitHub增强] 获取存储失败: ${key}`, error);
                return defaultValue;
            }
        },

        set(key, value) {
            try {
                GM_setValue(key, value);
                return true;
            } catch (error) {
                console.warn(`[GitHub增强] 设置存储失败: ${key}`, error);
                return false;
            }
        }
    };

    // ==================== 缓存管理 ====================
    class SimpleCache {
        constructor() {
            this.cache = new Map();
            this.expireTime = CONFIG.PERFORMANCE.CACHE_EXPIRE_TIME;
        }

        set(key, value) {
            this.cache.set(key, {
                value,
                expire: Date.now() + this.expireTime
            });
        }

        get(key) {
            const item = this.cache.get(key);
            if (!item) return null;

            if (Date.now() > item.expire) {
                this.cache.delete(key);
                return null;
            }

            return item.value;
        }

        clear() {
            this.cache.clear();
        }
    }

    // ==================== 主应用类 ====================
    class GitHubEnhancer {
        constructor() {
            this.cache = new SimpleCache();
            this.pageConfig = {};
            this.rawFastIndex = Storage.get(CONFIG.STORAGE_KEYS.RAW_FAST_INDEX, 0);

            // 初始化默认设置
            this.initDefaultSettings();
        }

        // 初始化默认设置
        initDefaultSettings() {
            const defaults = {
                [CONFIG.STORAGE_KEYS.AUTO_REDIRECT]: true,
                [CONFIG.STORAGE_KEYS.ENABLE_TRANSLATION]: true,
                [CONFIG.STORAGE_KEYS.RAW_FAST_INDEX]: 0,
                [CONFIG.STORAGE_KEYS.RAW_DOWN_LINK]: true,
                [CONFIG.STORAGE_KEYS.GIT_CLONE]: true,
            };

            Object.entries(defaults).forEach(([key, defaultValue]) => {
                if (Storage.get(key) === null) {
                    Storage.set(key, defaultValue);
                }
            });
        }

        // 初始化应用
        async init() {
            try {
                console.log(`[GitHub增强] v${CONFIG.VERSION} 开始初始化...`);

                // 检查自动跳转
                if (this.shouldRedirect()) {
                    this.performRedirect();
                    return;
                }

                // 检查依赖
                if (!this.checkDependencies()) {
                    return;
                }

                // 初始化功能
                this.setupLanguageEnvironment();
                this.updatePageConfig();
                this.setupEventListeners();
                this.registerMenuCommands();
                this.setupColorMode();

                // 延迟执行的功能
                setTimeout(() => this.addRawFile(), 1000);
                setTimeout(() => this.addRawDownLink(), 2000);

                // 检查Release页面
                if (location.pathname.indexOf('/releases') > -1) {
                    setTimeout(() => this.addRelease(), 1500);
                }

                // 执行初始翻译
                this.performInitialTranslation();

                console.log(`[GitHub增强] 初始化完成`);

            } catch (error) {
                console.error('[GitHub增强] 初始化失败:', error);
                this.showNotification('初始化失败,请刷新页面重试');
            }
        }

        // 检查是否需要重定向
        shouldRedirect() {
            return Storage.get(CONFIG.STORAGE_KEYS.AUTO_REDIRECT) && window.location.host === 'github.com';
        }

        // 执行重定向
        performRedirect() {
            const newUrl = window.location.href.replace('https://github.com', 'https://hub.mihoyo.online');
            console.log(`[GitHub增强] 重定向到: ${newUrl}`);
            window.location.replace(newUrl);
        }

        // 检查依赖
        checkDependencies() {
            if (typeof I18N === 'undefined') {
                this.showNotification('词库文件未加载,脚本无法运行!');
                return false;
            }
            console.log('[GitHub增强] 词库文件已加载');
            return true;
        }

        // 设置语言环境
        setupLanguageEnvironment() {
            document.documentElement.lang = CONFIG.LANG;

            new MutationObserver(() => {
                if (document.documentElement.lang === "en") {
                    document.documentElement.lang = CONFIG.LANG;
                }
            }).observe(document.documentElement, { attributeFilter: ['lang'] });
        }

        // 更新页面配置
        updatePageConfig() {
            const pageType = this.detectPageType();
            if (pageType) {
                this.pageConfig = this.buildPageConfig(pageType);
            }
        }

        // 检测页面类型
        detectPageType() {
            try {
                const url = new URL(window.location.href);
                const { hostname, pathname } = url;

                const pageMap = {
                    'gist.github.com': 'gist1',
                    'www.githubstatus.com': 'status',
                    'skills.github.com': 'skills',
                    'education.github.com': 'education',
                    'gist.mihoyo.online': 'gist2',
                };

                const site = pageMap[hostname] || 'github';
                const specialSites = ['gist1', 'status', 'skills', 'education', 'gist2'];

                if (specialSites.includes(site)) {
                    return site;
                }

                // 简化的页面类型检测
                if (pathname === '/') {
                    return document.body?.classList.contains("logged-in") ? 'dashboard' : 'homepage';
                } else if (pathname.includes('/releases')) {
                    return 'repository';
                } else {
                    return 'repository'; // 默认为仓库页面
                }

            } catch (error) {
                console.warn('[GitHub增强] 页面类型检测失败:', error);
                return 'repository';
            }
        }

        // 构建页面配置
        buildPageConfig(pageType) {
            try {
                return {
                    currentPageType: pageType,
                    staticDict: {
                        ...I18N[CONFIG.LANG].public.static,
                        ...(I18N[CONFIG.LANG][pageType]?.static || {})
                    },
                    regexpRules: [
                        ...(I18N[CONFIG.LANG][pageType]?.regexp || []),
                        ...I18N[CONFIG.LANG].public.regexp
                    ]
                };
            } catch (error) {
                console.warn('[GitHub增强] 构建页面配置失败:', error);
                return { currentPageType: pageType };
            }
        }

        // 设置事件监听
        setupEventListeners() {
            // URL变化监听
            if (window.onurlchange === undefined) {
                this.addUrlChangeEvent();
            }

            window.addEventListener('urlchange', () => {
                this.setupColorMode();
                if (location.pathname.indexOf('/releases') > -1) {
                    this.addRelease();
                }
                setTimeout(() => this.addRawFile(), 1000);
                setTimeout(() => this.addRawDownLink(), 2000);
            });

            // Turbo事件监听
            document.addEventListener('turbo:load', () => {
                this.translateTitle();
            });

            // 设置DOM变化监听器
            this.setupMutationObserver();
        }

        // 设置DOM变化监听器
        setupMutationObserver() {
            const observer = new MutationObserver((mutations) => {
                this.handleMutations(mutations);
            });

            const config = {
                childList: true,
                subtree: true,
                characterData: true
            };

            if (document.body) {
                observer.observe(document.body, config);
            } else {
                document.addEventListener('DOMContentLoaded', () => {
                    if (document.body) {
                        observer.observe(document.body, config);
                    }
                });
            }
        }

        // 处理DOM变化
        handleMutations(mutations) {
            // 处理Release页面的动态加载
            if (location.pathname.indexOf('/releases') > -1) {
                for (const mutation of mutations) {
                    for (const node of mutation.addedNodes) {
                        if (node.nodeType === Node.ELEMENT_NODE) {
                            if (node.tagName === 'DIV' &&
                                node.dataset &&
                                node.dataset.viewComponent === 'true' &&
                                node.classList &&
                                node.classList.contains('Box')) {
                                setTimeout(() => this.addRelease(), 100);
                                break;
                            }
                        }
                    }
                }
            }

            // 处理仓库页面的Git Clone等功能
            if (Utils.safeQuerySelector('#repository-container-header:not([hidden])')) {
                for (const mutation of mutations) {
                    for (const node of mutation.addedNodes) {
                        if (node.nodeType === Node.ELEMENT_NODE && node.tagName === 'DIV') {
                            if (node.parentElement && node.parentElement.id === '__primerPortalRoot__') {
                                // 这里可以添加Git Clone相关的处理
                                setTimeout(() => {
                                    this.addDownloadZIP(node);
                                    this.addGitClone(node);
                                }, 100);
                            }
                        }
                    }
                }
            }

            // 处理翻译
            if (Storage.get(CONFIG.STORAGE_KEYS.ENABLE_TRANSLATION)) {
                const nodesToTranslate = mutations.flatMap(({ addedNodes, type }) => {
                    if (type === 'childList' && addedNodes.length > 0) {
                        return [...addedNodes].filter(node =>
                            node.nodeType === Node.ELEMENT_NODE || node.nodeType === Node.TEXT_NODE
                        );
                    }
                    return [];
                });

                nodesToTranslate.forEach(node => {
                    this.traverseNode(node);
                });
            }
        }

        // 添加URL变化事件
        addUrlChangeEvent() {
            const originalPushState = history.pushState;
            const originalReplaceState = history.replaceState;

            history.pushState = function (...args) {
                const result = originalPushState.apply(this, args);
                window.dispatchEvent(new Event('urlchange'));
                return result;
            };

            history.replaceState = function (...args) {
                const result = originalReplaceState.apply(this, args);
                window.dispatchEvent(new Event('urlchange'));
                return result;
            };

            window.addEventListener('popstate', () => {
                window.dispatchEvent(new Event('urlchange'));
            });
        }

        // 执行初始翻译
        performInitialTranslation() {
            if (Storage.get(CONFIG.STORAGE_KEYS.ENABLE_TRANSLATION)) {
                this.translateTitle();
                this.traverseNode(document.body);
            }
        }

        // 翻译页面标题
        translateTitle() {
            if (!Storage.get(CONFIG.STORAGE_KEYS.ENABLE_TRANSLATION)) return;

            try {
                const text = document.title;
                const cacheKey = `title_${text}`;

                let translatedText = this.cache.get(cacheKey);
                if (translatedText) {
                    document.title = translatedText;
                    return;
                }

                translatedText = I18N[CONFIG.LANG]['title']['static'][text] || '';

                if (!translatedText) {
                    const rules = I18N[CONFIG.LANG]['title'].regexp || [];
                    for (const [pattern, replacement] of rules) {
                        translatedText = text.replace(pattern, replacement);
                        if (translatedText !== text) break;
                    }
                }

                if (translatedText && translatedText !== text) {
                    document.title = translatedText;
                    this.cache.set(cacheKey, translatedText);
                }
            } catch (error) {
                console.warn('[GitHub增强] 标题翻译失败:', error);
            }
        }

        // 遍历并翻译节点
        traverseNode(rootNode) {
            if (!Storage.get(CONFIG.STORAGE_KEYS.ENABLE_TRANSLATION) || !rootNode) return;

            try {
                if (rootNode.nodeType === Node.TEXT_NODE) {
                    this.translateTextNode(rootNode);
                    return;
                }

                const walker = document.createTreeWalker(
                    rootNode,
                    NodeFilter.SHOW_TEXT,
                    null,
                    false
                );

                let node;
                while (node = walker.nextNode()) {
                    this.translateTextNode(node);
                }
            } catch (error) {
                console.warn('[GitHub增强] 节点遍历失败:', error);
            }
        }

        // 翻译文本节点
        translateTextNode(node) {
            if (!node.textContent || node.textContent.length > CONFIG.PERFORMANCE.MAX_TEXT_LENGTH) return;

            const text = node.textContent.trim();
            if (!text || /^[\s0-9]*$/.test(text) || /^[\u4e00-\u9fa5]+$/.test(text)) return;

            const translatedText = this.translateText(text);
            if (translatedText && translatedText !== text) {
                node.textContent = node.textContent.replace(text, translatedText);
            }
        }

        // 翻译文本
        translateText(text) {
            if (!this.pageConfig.staticDict) return false;

            const cacheKey = `text_${text}`;
            let translatedText = this.cache.get(cacheKey);
            if (translatedText) return translatedText;

            // 静态翻译
            translatedText = this.pageConfig.staticDict[text];
            if (typeof translatedText === 'string') {
                this.cache.set(cacheKey, translatedText);
                return translatedText;
            }

            // 正则翻译
            if (this.pageConfig.regexpRules) {
                for (const [pattern, replacement] of this.pageConfig.regexpRules) {
                    try {
                        translatedText = text.replace(pattern, replacement);
                        if (translatedText !== text) {
                            this.cache.set(cacheKey, translatedText);
                            return translatedText;
                        }
                    } catch (error) {
                        console.warn('[GitHub增强] 正则翻译失败:', error);
                    }
                }
            }

            return false;
        }

        // 设置颜色模式
        setupColorMode() {
            try {
                let styleElement = document.getElementById('XIU2-Github');
                if (!styleElement) {
                    styleElement = Utils.createElement('style', {
                        id: 'XIU2-Github',
                        type: 'text/css'
                    });
                    document.head.appendChild(styleElement);
                }

                let backColor = '#ffffff', fontColor = '#888888';
                const rootElement = document.documentElement;

                if (rootElement.dataset.colorMode === 'dark') {
                    if (rootElement.dataset.darkTheme === 'dark_dimmed') {
                        backColor = '#272e37';
                        fontColor = '#768390';
                    } else {
                        backColor = '#161a21';
                        fontColor = '#97a0aa';
                    }
                }

                styleElement.textContent = `.XIU2-RS a {--XIU2-background-color: ${backColor}; --XIU2-font-color: ${fontColor};}`;
            } catch (error) {
                console.warn('[GitHub增强] 颜色模式设置失败:', error);
            }
        }

        // 添加Release加速下载
        addRelease() {
            try {
                console.log('[GitHub增强] 尝试添加Release加速下载...');

                const footers = Utils.safeQuerySelectorAll(CONFIG.SELECTORS.RELEASE_FOOTER);
                console.log(`[GitHub增强] 找到 ${footers.length} 个 footer 元素`);

                if (footers.length === 0) {
                    console.log('[GitHub增强] 未找到Release页面的footer元素');
                    return;
                }

                if (location.pathname.indexOf('/releases') === -1) {
                    console.log('[GitHub增强] 当前不在Release页面');
                    return;
                }

                const downloadSources = DOWNLOAD_SOURCES.release;
                const divDisplay = document.documentElement.clientWidth > 755
                    ? 'margin-top: -3px;margin-left: 8px;display: inherit;'
                    : 'margin-left: -90px;';

                // 添加样式(如果不存在)
                if (!document.querySelector('#XIU2-release-style')) {
                    const style = Utils.createElement('style', { id: 'XIU2-release-style' });
                    style.textContent = '@media (min-width: 768px) {.Box-footer li.Box-row>div>span.color-fg-muted {min-width: 27px !important;}}';
                    document.head.appendChild(style);
                }

                let addedCount = 0;
                footers.forEach(footer => {
                    if (footer.querySelector(`.${CONFIG.CSS_CLASSES.XIU2_RS}`)) {
                        console.log('[GitHub增强] 该footer已存在加速按钮,跳过');
                        return;
                    }

                    const links = Utils.safeQuerySelectorAll('li.Box-row a', footer);
                    console.log(`[GitHub增强] 在footer中找到 ${links.length} 个下载链接`);

                    links.forEach(link => {
                        const href = link.href.split(location.host);
                        if (href.length < 2) return;

                        let html = `<div class="${CONFIG.CSS_CLASSES.XIU2_RS}" style="${divDisplay}">`;

                        downloadSources.forEach(([sourceUrl, sourceName, sourceDesc]) => {
                            const url = sourceUrl + href[1];
                            const buttonStyle = 'padding:0 6px; margin-right: -1px; border-radius: 2px; background-color: var(--XIU2-background-color); border-color: var(--borderColor-default); font-size: 11px; color: var(--XIU2-font-color);';
                            html += `<a style="${buttonStyle}" class="btn" href="${url}" target="_blank" title="${sourceDesc}" rel="noreferrer noopener nofollow">${sourceName}</a>`;
                        });

                        html += '</div>';

                        const nextElement = link.parentElement.nextElementSibling;
                        if (nextElement) {
                            nextElement.insertAdjacentHTML('beforeend', html);
                            addedCount++;
                        }
                    });
                });

                console.log(`[GitHub增强] 成功添加了 ${addedCount} 个Release加速按钮`);

            } catch (error) {
                console.warn('[GitHub增强] Release加速添加失败:', error);
            }
        }

        // 添加Raw文件加速
        addRawFile() {
            try {
                const rawButton = Utils.safeQuerySelector(CONFIG.SELECTORS.RAW_BUTTON);
                if (!rawButton) return;

                Utils.safeQuerySelectorAll(`.${CONFIG.CSS_CLASSES.XIU2_RF}`).forEach(el => el.remove());

                const href = location.href.replace(`https://${location.host}`, '');
                const href2 = href.replace('/blob/', '/');
                let html = '';

                for (let i = 1; i < DOWNLOAD_SOURCES.raw.length; i++) {
                    const [sourceUrl, sourceName, sourceDesc] = DOWNLOAD_SOURCES.raw[i];
                    const url = sourceUrl + href2;
                    html += `<a href="${url}" title="${sourceDesc}" target="_blank" role="button" rel="noreferrer noopener nofollow" data-size="small" data-variant="default" class="${rawButton.className} ${CONFIG.CSS_CLASSES.XIU2_RF}" style="border-radius: 0;margin-left: -1px;">${sourceName}</a>`;
                }

                rawButton.insertAdjacentHTML('afterend', html);

            } catch (error) {
                console.warn('[GitHub增强] Raw文件加速添加失败:', error);
            }
        }

        // 添加Raw文件下载链接
        addRawDownLink() {
            if (!Storage.get(CONFIG.STORAGE_KEYS.RAW_DOWN_LINK)) return;

            try {
                const files = Utils.safeQuerySelectorAll(CONFIG.SELECTORS.FILE_ICONS);
                if (files.length === 0 || location.pathname.indexOf('/tags') > -1) return;

                if (Utils.safeQuerySelectorAll(`.${CONFIG.CSS_CLASSES.FILE_DOWNLOAD_LINK}`).length > 0) return;

                const mouseOverHandler = (evt) => {
                    const elem = evt.currentTarget;
                    const downloadLinks = Utils.safeQuerySelectorAll(`.${CONFIG.CSS_CLASSES.FILE_DOWNLOAD_LINK}`, elem);
                    const fileIcons = Utils.safeQuerySelectorAll('svg.octicon.octicon-file, svg.color-fg-muted', elem);

                    downloadLinks.forEach(el => el.style.display = 'inline');
                    fileIcons.forEach(el => el.style.display = 'none');
                };

                const mouseOutHandler = (evt) => {
                    const elem = evt.currentTarget;
                    const downloadLinks = Utils.safeQuerySelectorAll(`.${CONFIG.CSS_CLASSES.FILE_DOWNLOAD_LINK}`, elem);
                    const fileIcons = Utils.safeQuerySelectorAll('svg.octicon.octicon-file, svg.color-fg-muted', elem);

                    downloadLinks.forEach(el => el.style.display = 'none');
                    fileIcons.forEach(el => el.style.display = 'inline');
                };

                files.forEach(fileIcon => {
                    const row = fileIcon.parentNode?.parentNode;
                    if (!row) return;

                    const linkElement = Utils.safeQuerySelector('[role="rowheader"] > .css-truncate.css-truncate-target.d-block.width-fit > a, .react-directory-truncate>a', row);
                    if (!linkElement) return;

                    const fileName = linkElement.innerText;
                    const href = linkElement.getAttribute('href');
                    if (!href) return;

                    const href2 = href.replace('/blob/', '/');
                    const currentSource = DOWNLOAD_SOURCES.raw[this.rawFastIndex];
                    const url = currentSource[0] + href2;

                    const svgIcon = '<svg class="octicon octicon-cloud-download" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path d="M9 12h2l-3 3-3-3h2V7h2v5zm3-8c0-.44-.91-3-4.5-3C5.08 1 3 2.92 3 5 1.02 5 0 6.52 0 8c0 1.53 1 3 3 3h3V9.7H3C1.38 9.7 1.3 8.28 1.3 8c0-.17.05-1.7 1.7-1.7h1.3V5c0-1.39 1.56-2.7 3.2-2.7 2.55 0 3.13 1.55 3.2 1.8v1.2H12c.81 0 2.7.22 2.7 2.2 0 2.09-2.25 2.2-2.7 2.2h-2V11h2c2.08 0 4-1.16 4-3.5C16 5.06 14.08 4 12 4z"></path></svg>';

                    fileIcon.insertAdjacentHTML('afterend',
                        `<a href="${url}" download="${fileName}" target="_blank" rel="noreferrer noopener nofollow" class="${CONFIG.CSS_CLASSES.FILE_DOWNLOAD_LINK}" style="display: none;" title="「${currentSource[1]}」加速下载">${svgIcon}</a>`
                    );

                    row.addEventListener('mouseover', mouseOverHandler);
                    row.addEventListener('mouseout', mouseOutHandler);
                });

            } catch (error) {
                console.warn('[GitHub增强] Raw下载链接添加失败:', error);
            }
        }

        // 切换Raw加速源
        toggleRawSource() {
            try {
                if (this.rawFastIndex >= DOWNLOAD_SOURCES.raw.length - 1) {
                    this.rawFastIndex = 0;
                } else {
                    this.rawFastIndex += 1;
                }

                Storage.set(CONFIG.STORAGE_KEYS.RAW_FAST_INDEX, this.rawFastIndex);

                // 移除旧的下载链接
                Utils.safeQuerySelectorAll(`.${CONFIG.CSS_CLASSES.FILE_DOWNLOAD_LINK}`).forEach(el => el.remove());

                setTimeout(() => this.addRawDownLink(), 100);

                const currentSource = DOWNLOAD_SOURCES.raw[this.rawFastIndex];
                this.showNotification(`已切换加速源为:${currentSource[1]}`);

            } catch (error) {
                console.warn('[GitHub增强] 切换加速源失败:', error);
            }
        }

        // 注册菜单命令
        registerMenuCommands() {
            const menuConfigs = [
                {
                    label: "所有翻译",
                    key: CONFIG.STORAGE_KEYS.ENABLE_TRANSLATION,
                    callback: (newState) => {
                        if (newState) {
                            this.traverseNode(document.body);
                        }
                    }
                },
                {
                    label: "自动跳转",
                    key: CONFIG.STORAGE_KEYS.AUTO_REDIRECT,
                    callback: (newState) => {
                        this.showNotification(`自动跳转已${newState ? '启用' : '禁用'},刷新页面生效`);
                    }
                }
            ];

            menuConfigs.forEach(config => this.createMenuCommand(config));

            GM_registerMenuCommand("切换加速源", () => {
                this.toggleRawSource();
            });
        }

        // 创建菜单命令
        createMenuCommand({ label, key, callback }) {
            let menuId;

            const getMenuLabel = (label, isEnabled) => `${isEnabled ? "禁用" : "启用"} ${label}`;

            const toggle = () => {
                const currentState = Storage.get(key);
                const newState = !currentState;

                Storage.set(key, newState);
                this.showNotification(`${label}已${newState ? '启用' : '禁用'}`);

                if (callback) callback(newState);

                GM_unregisterMenuCommand(menuId);
                menuId = GM_registerMenuCommand(getMenuLabel(label, newState), toggle);
            };

            const currentState = Storage.get(key);
            menuId = GM_registerMenuCommand(getMenuLabel(label, currentState), toggle);
        }

        // 添加Download ZIP加速
        addDownloadZIP(target) {
            try {
                const html = Utils.safeQuerySelector('ul[class^=prc-ActionList-ActionList-]>li:last-child', target);
                if (!html) return;

                const scriptElement = Utils.safeQuerySelector('react-partial[partial-name=repos-overview]>script[data-target="react-partial.embeddedData"]');
                if (!scriptElement) return;

                const scriptContent = scriptElement.textContent;
                const zipUrlStart = scriptContent.indexOf('"zipballUrl":"');
                if (zipUrlStart === -1) return;

                const zipUrlSlice = scriptContent.slice(zipUrlStart + 14);
                const zipUrlEnd = zipUrlSlice.indexOf('"');
                if (zipUrlEnd === -1) return;

                const href = zipUrlSlice.slice(0, zipUrlEnd);
                const downloadSources = DOWNLOAD_SOURCES.release;
                let htmlContent = '';

                downloadSources.forEach(([sourceUrl, sourceName, sourceDesc]) => {
                    const clonedElement = html.cloneNode(true);
                    const linkElement = Utils.safeQuerySelector('a[href$=".zip"]', clonedElement);
                    const spanElement = Utils.safeQuerySelector('span[id]', clonedElement);

                    if (linkElement && spanElement) {
                        const url = sourceUrl + href;
                        linkElement.href = url;
                        linkElement.setAttribute('title', sourceDesc);
                        linkElement.setAttribute('target', '_blank');
                        linkElement.setAttribute('rel', 'noreferrer noopener nofollow');
                        spanElement.textContent = `Download ZIP ${sourceName}`;
                        htmlContent += clonedElement.outerHTML;
                    }
                });

                if (htmlContent) {
                    html.insertAdjacentHTML('afterend', htmlContent);
                }

            } catch (error) {
                console.warn('[GitHub增强] Download ZIP加速添加失败:', error);
            }
        }

        // 添加Git Clone加速
        addGitClone(target) {
            try {
                const html = Utils.safeQuerySelector('input[value^="https:"]:not([title])', target);
                if (!html) return;

                // 将镜像站URL替换为官方URL
                const originalUrl = html.value.replace('https://hub.mihoyo.online/', 'https://github.com/');
                const hrefSplit = originalUrl.split('https://github.com')[1];
                if (!hrefSplit) return;

                const htmlParent = `<div style="margin-top: 4px;" class="XIU2-GC ${html.parentElement.className}">`;
                let htmlContent = '';
                let gitClonePrefix = '';

                if (html.nextElementSibling) {
                    html.nextElementSibling.hidden = true;
                }

                if (html.parentElement.nextElementSibling && html.parentElement.nextElementSibling.tagName === 'SPAN') {
                    html.parentElement.nextElementSibling.textContent += ' (↑点击上面文字可复制)';
                }

                if (Storage.get(CONFIG.STORAGE_KEYS.GIT_CLONE)) {
                    gitClonePrefix = 'git clone ';
                    html.value = gitClonePrefix + html.value;
                    html.setAttribute('value', html.value);
                }

                DOWNLOAD_SOURCES.clone.forEach(([sourceUrl, sourceName, sourceDesc]) => {
                    const clonedInput = html.cloneNode(true);
                    let url;

                    if (sourceUrl === 'https://gitclone.com') {
                        url = sourceUrl + '/github.com' + hrefSplit;
                    } else {
                        url = sourceUrl + hrefSplit;
                    }

                    clonedInput.title = `${url}\n\n${sourceDesc}\n\n提示:点击文字可直接复制`;
                    clonedInput.setAttribute('value', gitClonePrefix + url);
                    htmlContent += htmlParent + clonedInput.outerHTML + '</div>';
                });

                if (htmlContent) {
                    html.parentElement.insertAdjacentHTML('afterend', htmlContent);

                    // 添加复制功能
                    if (!html.parentElement.parentElement.classList.contains('XIU2-GCP')) {
                        html.parentElement.parentElement.classList.add('XIU2-GCP');
                        html.parentElement.parentElement.addEventListener('click', (e) => {
                            if (e.target.tagName === 'INPUT') {
                                try {
                                    GM_setClipboard(e.target.value);
                                    this.showNotification('已复制到剪贴板');
                                } catch (error) {
                                    console.warn('[GitHub增强] 复制失败:', error);
                                }
                            }
                        });
                    }
                }

            } catch (error) {
                console.warn('[GitHub增强] Git Clone加速添加失败:', error);
            }
        }

        // 显示通知
        showNotification(message) {
            try {
                GM_notification({
                    text: `[GitHub增强] ${message}`,
                    timeout: 3000
                });
            } catch (error) {
                console.log(`[GitHub增强] ${message}`);
            }
        }
    }

    // ==================== 应用启动 ====================

    const app = new GitHubEnhancer();

    const startApp = async () => {
        try {
            await app.init();
        } catch (error) {
            console.error('[GitHub增强] 应用启动失败:', error);
        }
    };

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', startApp);
    } else {
        startApp();
    }

    // 导出到全局作用域(用于调试)
    if (typeof window !== 'undefined') {
        window.GitHubEnhancer = app;
    }

})();