Greasy Fork is available in English.

GitHub Release Smart Highlighter

Smartly highlight the best GitHub Release download matching your OS & architecture — combining accurate matching, conflict exclusion, and rich visual feedback.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey, Greasemonkey или Violentmonkey.

Для установки этого скрипта вам необходимо установить расширение, такое как Tampermonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Userscripts.

Чтобы установить этот скрипт, сначала вы должны установить расширение браузера, например Tampermonkey.

Чтобы установить этот скрипт, вы должны установить расширение — менеджер скриптов.

(у меня уже есть менеджер скриптов, дайте мне установить скрипт!)

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

(у меня уже есть менеджер стилей, дайте мне установить скрипт!)

// ==UserScript==
// @name:zh-CN        GitHub Release 智能高亮
// @name              GitHub Release Smart Highlighter
// @namespace         https://github.com/leeflouring
// @version           1.0.0
// @description       Smartly highlight the best GitHub Release download matching your OS & architecture — combining accurate matching, conflict exclusion, and rich visual feedback.
// @description:zh-CN 智能高亮最符合你系统架构的 GitHub Release 下载——融合精准匹配、互斥排除与丰富视觉反馈,支持 macOS WebGL 架构探测、手动按钮容错。
// @author            leeflouring
// @license           MIT
// @match             https://github.com/*/*/releases/tag/*
// @match             https://github.com/*/*/releases/latest
// @match             https://github.com/*/*/releases
// @match             https://github.com/*/releases
// @match             https://github.com/*/releases/*
// ==/UserScript==

(function () {
    'use strict';

    // ═══════════════════════════════════════════════════════════
    // §1  平台探测
    // ═══════════════════════════════════════════════════════════

    function detectArchitecture(os) {
        // macOS:通过 WebGL GPU 渲染器精确区分 Apple Silicon / Intel
        if (os === 'darwin') {
            try {
                const canvas = document.createElement('canvas');
                const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
                if (gl) {
                    const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
                    if (debugInfo) {
                        const renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL).toLowerCase();
                        if (renderer.includes('apple') || renderer.includes('m1') || renderer.includes('m2') || renderer.includes('m3') || renderer.includes('m4')) {
                            return 'arm64';
                        }
                    }
                }
            } catch (e) {}
            return 'amd64';
        }
        // 非 macOS:从 UA 推断
        const ua = (navigator.userAgent || '').toLowerCase();
        const plat = (navigator.platform || '').toLowerCase();
        if (/aarch64|arm64/.test(ua) || /aarch64|arm64/.test(plat)) return 'arm64';
        if (/loongarch64|loong64/.test(ua)) return 'loong64';
        if (/riscv64/.test(ua)) return 'riscv64';
        if (/[-_.]arm[-_.v]/.test(ua) || /[-_.]arm$/.test(ua)) return 'arm';
        return 'amd64';
    }

    function detectPlatform() {
        const ua = navigator.userAgent || '';
        const plat = navigator.platform || '';

        let os = '';
        if (/Windows/.test(ua)) os = 'windows';
        else if (/Mac OS X|Macintosh|MacIntel|MacPPC|Mac68K/.test(ua) || /Mac/.test(plat)) os = 'darwin';
        else if (/Linux/.test(ua) || /Linux/.test(plat) || /X11/.test(ua)) os = 'linux';
        else if (/CrOS/.test(ua)) os = 'linux';

        // Windows 版本号(NT 6.1=Win7, 6.2=Win8, 6.3=Win8.1, 10+=Win10/11)
        let winVersion = null;
        if (os === 'windows') {
            const ntMatch = ua.match(/Windows NT (\d+\.\d+)/);
            if (ntMatch) {
                const ntVer = parseFloat(ntMatch[1]);
                if (ntVer <= 6.1) winVersion = '7';
                else if (ntVer <= 6.3) winVersion = '8';
                else winVersion = '10+';
            }
        }

        const arch = detectArchitecture(os);
        return { os, arch, winVersion };
    }

    // ═══════════════════════════════════════════════════════════
    // §2  匹配与评分
    // ═══════════════════════════════════════════════════════════

    // 综合忽略后缀(融合两份列表)
    const IGNORED_EXTS = /\.(sha256|sha512|sha1|md5|asc|sig|txt|dgst|sbom|pem|key|blockmap)$/;
    const IGNORED_KEYWORDS = ['source code', 'source.tar'];

    // 互斥关键词:文件名含其他平台关键词时直接排除
    const CONFLICT_KEYWORDS = {
        darwin:  ['windows', 'win64', 'win32', 'win-', 'linux', 'ubuntu', 'debian', 'rhel', 'fedora', '.rpm', '.deb'],
        windows: ['macos', 'darwin', 'osx', 'mac-', 'linux', 'ubuntu', 'debian', '.appimage', '.deb', '.rpm'],
        linux:   ['windows', 'win64', 'win32', 'win-', 'macos', 'darwin', 'osx', 'mac-', '.exe', '.msi']
    };

    // OS 专属后缀
    const OS_SPECIFIC_EXTS = {
        darwin:  ['.dmg', '.app', '.pkg'],
        windows: ['.exe', '.msi'],
        linux:   ['.appimage', '.deb', '.rpm', '.run']
    };
    // 通用后缀
    const UNIVERSAL_EXTS = ['.zip', '.tar.gz', '.tgz', '.7z', '.gz', '.xz', '.rar'];

    // 架构关键词映射
    const ARCH_KEYWORDS = {
        arm64:    ['aarch64', 'arm64', 'armv8', 'm1', 'm2', 'm3', 'm4', 'apple silicon'],
        amd64:    ['x86_64', 'amd64', 'x64', 'win64', 'intel'],
        loong64:  ['loong64', 'loongarch64'],
        riscv64:  ['riscv64'],
        x86:      ['x86', 'i386', 'i686', 'win32', '32-bit'],
        arm:      ['armv7', 'armhf', 'armv6']
    };

    function calculateScore(filename, profile) {
        const lower = filename.toLowerCase();

        // A. 忽略项
        if (IGNORED_KEYWORDS.some(k => lower.includes(k))) return -1;
        if (IGNORED_EXTS.test(lower)) return -1;

        // B. 互斥关键词排除
        if (CONFLICT_KEYWORDS[profile.os] && CONFLICT_KEYWORDS[profile.os].some(k => lower.includes(k))) return -1;

        // C. 后缀分类
        const isOsSpecific = OS_SPECIFIC_EXTS[profile.os] && OS_SPECIFIC_EXTS[profile.os].some(ext => lower.endsWith(ext));
        const isUniversal = UNIVERSAL_EXTS.some(ext => lower.endsWith(ext));
        if (!isOsSpecific && !isUniversal) return -1;

        // D. 基础分:OS专属格式权重更高
        let score = isOsSpecific ? 100 : 60;

        // E. 架构匹配加分/减分
        const isArchMatch = ARCH_KEYWORDS[profile.arch] && ARCH_KEYWORDS[profile.arch].some(k => lower.includes(k));
        const isAmd64File = ARCH_KEYWORDS.amd64.some(k => lower.includes(k));
        const isArm64File = ARCH_KEYWORDS.arm64.some(k => lower.includes(k));
        const isX86File = ARCH_KEYWORDS.x86.some(k => lower.includes(k) && !lower.includes('x86_64'));

        if (isArchMatch) {
            score += 80;                          // 完全匹配
        } else if (profile.arch === 'arm64' && isAmd64File) {
            score += 20;                          // arm64 设备跑 amd64 可用但非最优
        } else if (profile.arch === 'amd64' && isX86File) {
            score += 10;                          // amd64 设备跑 x86 可用但非最优
        } else if (profile.arch === 'amd64' && isArm64File) {
            return -1;                            // amd64 设备不能跑 arm64
        } else if (profile.arch === 'arm64' && isX86File) {
            return -1;                            // arm64 设备不能跑 x86
        } else if (!isAmd64File && !isArm64File && !isX86File) {
            score += 30;                          // 无架构标记的通用包(如 xxx.zip)
        }

        // F. 文件格式偏好微调
        if (profile.os === 'windows') {
            if (lower.endsWith('.exe')) score += 10;
            else if (lower.endsWith('.msi')) score += 5;
        }
        if (profile.os === 'darwin' && lower.endsWith('.dmg')) score += 5;
        if (profile.os === 'linux') {
            if (lower.endsWith('.deb')) score += 5;
            else if (lower.endsWith('.appimage')) score += 4;
            else if (lower.endsWith('.rpm')) score += 3;
        }

        // G. Windows 7 特殊处理
        if (profile.os === 'windows' && /win(?:dows)?[-_]?7/i.test(lower)) {
            score += (profile.winVersion === '7') ? 40 : -80;
        }

        // H. desktop 版本略降权重(通常非核心包)
        if (lower.includes('-desktop')) score -= 3;

        return score;
    }

    // ═══════════════════════════════════════════════════════════
    // §3  视觉反馈
    // ═══════════════════════════════════════════════════════════

    function injectStyles() {
        if (document.getElementById('gh-smart-style')) return;
        const style = document.createElement('style');
        style.id = 'gh-smart-style';
        style.textContent = `
            @keyframes smart-glow {
                0%,100% { box-shadow: 0 0 4px rgba(46,160,67,.3) }
                50%     { box-shadow: 0 0 16px rgba(46,160,67,.7) }
            }
            .gh-smart-highlight {
                background: rgba(46,160,67,.1) !important;
                border-left: 4px solid #2ea043 !important;
                border-radius: 6px !important;
                animation: smart-glow 2s ease-in-out 4;
                transition: background .3s ease;
            }
            .gh-smart-badge {
                display: inline-block;
                margin-left: 8px;
                padding: 2px 10px;
                font-size: 12px;
                font-weight: 600;
                color: #fff;
                background: linear-gradient(135deg, #2ea043, #238636);
                border-radius: 12px;
                vertical-align: middle;
                line-height: 18px;
                letter-spacing: .3px;
            }
            body[data-color-mode="dark"] .gh-smart-highlight {
                background: rgba(46,160,67,.18) !important;
            }
            .gh-smart-btn {
                display: inline-flex;
                align-items: center;
                gap: 4px;
                margin-left: 8px;
                padding: 2px 10px;
                font-size: 12px;
                font-weight: 600;
                color: #fff;
                background: linear-gradient(135deg, #238636, #1a7f37);
                border: 1px solid rgba(255,255,255,.15);
                border-radius: 12px;
                cursor: pointer;
                line-height: 20px;
                vertical-align: middle;
                transition: all .15s ease;
                white-space: nowrap;
            }
            .gh-smart-btn:hover {
                background: linear-gradient(135deg, #2ea043, #238636);
                box-shadow: 0 0 8px rgba(46,160,67,.4);
                transform: scale(1.05);
            }
            .gh-smart-btn:active { transform: scale(.97) }
            .gh-smart-btn.done {
                background: linear-gradient(135deg, #1a7f37, #156d2e);
                opacity: .7;
                cursor: default;
            }
            .gh-smart-btn.warn {
                background: linear-gradient(135deg, #bd561d, #9e4216);
                opacity: .8;
            }
        `;
        document.head.appendChild(style);
    }

    // ═══════════════════════════════════════════════════════════
    // §4  高亮核心逻辑(防抖 + 清除旧标记 + 评分排序)
    // ═══════════════════════════════════════════════════════════

    function clearHighlight(container) {
        const rows = container.querySelectorAll('li.Box-row');
        rows.forEach(row => {
            row.classList.remove('gh-smart-highlight');
            const badge = row.querySelector('.gh-smart-badge');
            if (badge) badge.remove();
        });
    }

    function highlightInContainer(container, shouldScroll = false) {
        const profile = detectPlatform();
        const rows = container.querySelectorAll('li.Box-row');
        if (rows.length === 0) return 0;

        clearHighlight(container);

        let bestRow = null, bestScore = -1;
        rows.forEach(row => {
            const link = row.querySelector('a');
            if (!link) return;
            const name = link.textContent.trim();
            const score = calculateScore(name, profile.os, profile.arch, profile.winVersion);
            if (score > bestScore) { bestScore = score; bestRow = row; }
        });

        if (bestRow && bestScore > 0) {
            bestRow.classList.add('gh-smart-highlight');
            const a = bestRow.querySelector('a');
            if (a && !a.querySelector('.gh-smart-badge')) {
                const badge = document.createElement('span');
                badge.className = 'gh-smart-badge';
                badge.textContent = `✔ ${profile.os} ${profile.arch}`;
                a.appendChild(badge);
            }
            // 只有用户主动操作时才滚动,避免自动监听器不断抢夺滚动位置
            if (shouldScroll) {
                bestRow.scrollIntoView({ block: 'center', behavior: 'smooth' });
            }
            syncButtonState(container);
            return rows.length;
        }
        return 0;
    }

    // ═══════════════════════════════════════════════════════════
    // §5  手动按钮
    // ═══════════════════════════════════════════════════════════

    function createManualButton(details) {
        const profile = detectPlatform();
        const btn = document.createElement('span');
        btn.className = 'gh-smart-btn';
        btn.textContent = `📥 ${profile.os}/${profile.arch}`;
        btn.title = '手动高亮推荐下载(自动检测失败时使用)';

        btn.addEventListener('click', (e) => {
            e.preventDefault();
            e.stopPropagation();
            const count = highlightInContainer(details, true);
            if (count > 0) {
                btn.textContent = '✔ Done';
                btn.classList.add('done');
            } else {
                btn.textContent = '⚠ 无匹配文件';
                btn.classList.add('warn');
                setTimeout(() => {
                    btn.textContent = `📥 ${profile.os}/${profile.arch}`;
                    btn.classList.remove('warn');
                }, 2000);
            }
        });

        return btn;
    }

    function syncButtonState(container) {
        const btn = container.querySelector('.gh-smart-btn');
        if (!btn) return;
        if (container.querySelector('.gh-smart-highlight')) {
            btn.textContent = '✔ Done';
            btn.classList.add('done');
        }
    }

    // ═══════════════════════════════════════════════════════════
    // §6  容器注册(按钮 + 多层监听 + 即时尝试)
    // ═══════════════════════════════════════════════════════════

    function setupDetails(details) {
        injectStyles();

        // 挂载手动按钮
        if (!details.querySelector('.gh-smart-btn')) {
            const summary = details.querySelector('summary');
            if (summary) {
                const assetsSpan = summary.querySelector('.f3.text-bold');
                if (assetsSpan) {
                    assetsSpan.parentElement.appendChild(createManualButton(details));
                }
            }
        }

        // 监听 details[open]
        openObserver.observe(details, { attributes: true, attributeFilter: ['open'] });

        // include-fragment 加载监听
        const frag = details.querySelector('include-fragment[src*="expanded_assets"]');
        if (frag) setupFragmentListener(frag, details);

        // 即时尝试
        highlightInContainer(details);
    }

    function setupFragmentListener(fragment, details) {
        if (details.querySelectorAll('li.Box-row').length > 0) {
            highlightInContainer(details);
            return;
        }
        fragment.addEventListener('load', () => {
            setTimeout(() => highlightInContainer(details), 100);
        });
    }

    // ═══════════════════════════════════════════════════════════
    // §7  全局监听(防抖 + 多事件覆盖)
    // ═══════════════════════════════════════════════════════════

    // details[open] 变化
    const openObserver = new MutationObserver((mutations) => {
        mutations.forEach(m => {
            const details = m.target;
            if (details.open) {
                const frag = details.querySelector('include-fragment[src*="expanded_assets"]');
                if (frag) setupFragmentListener(frag, details);
                debouncedHighlight(details, 500);
                debouncedHighlight(details, 2000);
            }
        });
    });

    // DOM 变化(防抖版)
    let domTimer = null;
    const domObserver = new MutationObserver((mutations) => {
        if (mutations.some(m => m.addedNodes.length > 0)) {
            if (domTimer) clearTimeout(domTimer);
            domTimer = setTimeout(() => {
                const allDetails = document.querySelectorAll('details[data-target="details-toggle.detailsTarget"]');
                allDetails.forEach(d => highlightInContainer(d));
            }, 500);
        }

        // 单独处理新增的 include-fragment 和 details
        mutations.forEach(m => {
            m.addedNodes.forEach(node => {
                if (node.nodeType !== 1) return;

                if (node.tagName === 'INCLUDE-FRAGMENT' &&
                    node.getAttribute('src') && node.getAttribute('src').includes('expanded_assets')) {
                    const parentDetails = node.closest('details');
                    if (parentDetails) setupFragmentListener(node, parentDetails);
                }

                if (node.tagName === 'DETAILS' && node.dataset.target === 'details-toggle.detailsTarget') {
                    setupDetails(node);
                }
            });
        });
    });

    function debouncedHighlight(container, delay) {
        setTimeout(() => highlightInContainer(container), delay);
    }

    // ═══════════════════════════════════════════════════════════
    // §8  主入口
    // ═══════════════════════════════════════════════════════════

    function init() {
        injectStyles();
        const allDetails = document.querySelectorAll('details[data-target="details-toggle.detailsTarget"]');
        allDetails.forEach(d => setupDetails(d));
    }

    init();

    // GitHub SPA 导航全覆盖
    document.addEventListener('pjax:end', init);
    document.addEventListener('turbo:load', init);
    document.addEventListener('turbo:render', init);

    // 启动 DOM 监听
    domObserver.observe(document.body, { childList: true, subtree: true });
})();