Greasy Fork

Greasy Fork is available in English.

HuggingFace镜像链接提取器

在HuggingFace页面提取下载链接,同时显示原始链接和hf-mirror.com镜像链接。v1.3.3: 彻底修复搜索高亮间距问题,改用精确匹配和渐变背景

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         HuggingFace镜像链接提取器
// @namespace    http://tampermonkey.net/
// @version      1.3.3
// @description  在HuggingFace页面提取下载链接,同时显示原始链接和hf-mirror.com镜像链接。v1.3.3: 彻底修复搜索高亮间距问题,改用精确匹配和渐变背景
// @author       AI Assistant
// @match        https://huggingface.co/*
// @match        https://hf-mirror.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=huggingface.co
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // 创建样式
    const style = document.createElement('style');
    style.textContent = `
        .hf-extractor-btn {
            position: fixed;
            top: 20px;
            left: 20px;
            width: 60px;
            height: 60px;
            background: linear-gradient(45deg, #ff6b6b, #feca57);
            color: white;
            border: none;
            border-radius: 50%;
            cursor: pointer;
            font-size: 12px;
            font-weight: bold;
            z-index: 10000;
            box-shadow: 0 4px 15px rgba(0,0,0,0.3);
            transition: all 0.3s ease;
        }
        .hf-extractor-btn:hover {
            transform: scale(1.1);
            box-shadow: 0 6px 20px rgba(0,0,0,0.4);
        }
        .hf-modal {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0,0,0,0.5);
            z-index: 9999;
            display: none;
            justify-content: center;
            align-items: center;
        }
        .hf-modal-content {
            background: white;
            border-radius: 15px;
            padding: 20px;
            max-width: 95vw;
            max-height: 95vh;
            overflow: hidden;
            box-shadow: 0 10px 30px rgba(0,0,0,0.3);
            min-width: 800px;
            display: flex;
            flex-direction: column;
        }
        .hf-header {
            text-align: center;
            margin-bottom: 15px;
            color: #333;
            border-bottom: 2px solid #eee;
            padding-bottom: 10px;
        }
        .hf-stats {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            padding: 10px;
            border-radius: 8px;
            text-align: center;
            margin-bottom: 15px;
            font-weight: bold;
            font-size: 14px;
        }
        .hf-buttons {
            display: flex;
            gap: 8px;
            margin-bottom: 15px;
            flex-wrap: wrap;
        }
        .hf-sort-buttons {
            display: flex;
            gap: 5px;
            margin-bottom: 10px;
            justify-content: center;
        }
        .hf-search-container {
            margin-bottom: 15px;
            position: relative;
        }
        .hf-search-input {
            width: 100%;
            padding: 10px 40px 10px 15px;
            border: 2px solid #ddd;
            border-radius: 8px;
            font-size: 14px;
            outline: none;
            transition: all 0.3s ease;
            box-sizing: border-box;
            color: #2c3e50 !important;
            background: #ffffff !important;
        }
        .hf-search-input:focus {
            border-color: #3498db;
            box-shadow: 0 0 10px rgba(52, 152, 219, 0.3);
        }
        .hf-search-input::placeholder {
            color: #7f8c8d !important;
        }
        .hf-search-clear {
            position: absolute;
            right: 10px;
            top: 50%;
            transform: translateY(-50%);
            background: none;
            border: none;
            font-size: 18px;
            cursor: pointer;
            color: #999;
            padding: 5px;
            border-radius: 50%;
            transition: all 0.2s ease;
        }
        .hf-search-clear:hover {
            background: #f0f0f0;
            color: #666;
        }
        .hf-search-stats {
            font-size: 12px;
            color: #2c3e50 !important;
            text-align: center;
            margin-top: 5px;
            font-weight: 500;
        }
        .hf-highlight {
            background: linear-gradient(to bottom, transparent 0%, transparent 20%, #ffeb3b 20%, #ffeb3b 80%, transparent 80%, transparent 100%) !important;
            color: inherit !important;
            font-weight: 600 !important;
            padding: 0 !important;
            margin: 0 !important;
            border: none !important;
            border-radius: 0 !important;
            font-size: inherit !important;
            font-family: inherit !important;
            display: inline !important;
            line-height: inherit !important;
            letter-spacing: inherit !important;
            word-spacing: inherit !important;
            text-decoration: none !important;
            vertical-align: baseline !important;
            box-shadow: none !important;
            outline: none !important;
            text-shadow: none !important;
            position: relative !important;
        }

        /* 针对黑暗模式的额外优化 */
        @media (prefers-color-scheme: dark) {
            .hf-modal-content {
                background: #ffffff !important;
                color: #2c3e50 !important;
            }
            .hf-link-item {
                background: #f8f9fa !important;
                color: #2c3e50 !important;
            }
            .hf-link-row {
                background: #ffffff !important;
                color: #2c3e50 !important;
            }
        }
        .hf-sort-btn {
            padding: 5px 10px;
            border: 1px solid #ddd;
            border-radius: 5px;
            cursor: pointer;
            font-size: 12px;
            background: white;
            transition: all 0.2s ease;
        }
        .hf-sort-btn:hover {
            background: #f0f0f0;
        }
        .hf-sort-btn.active {
            background: #3498db;
            color: white;
            border-color: #3498db;
        }
        .hf-links-wrapper {
            flex: 1;
            overflow-y: auto;
            max-height: calc(95vh - 300px);
        }
        .hf-btn {
            padding: 8px 12px;
            border: none;
            border-radius: 6px;
            cursor: pointer;
            font-weight: bold;
            transition: all 0.3s ease;
            flex: 1;
            min-width: 100px;
            font-size: 13px;
        }
        .hf-btn:hover {
            transform: translateY(-2px);
            box-shadow: 0 4px 12px rgba(0,0,0,0.2);
        }
        .hf-btn-close { background: #e74c3c; color: white; }
        .hf-btn-copy-first { background: #3498db; color: white; }
        .hf-btn-copy-all-orig { background: #27ae60; color: white; }
        .hf-btn-copy-all-mirror { background: #f39c12; color: white; }
        .hf-link-item {
            background: #f8f9fa;
            border: 1px solid #dee2e6;
            border-radius: 6px;
            padding: 8px;
            margin-bottom: 6px;
            transition: all 0.2s ease;
        }
        .hf-link-item.main-file {
            border: 2px solid #ff6b6b;
            background: linear-gradient(135deg, #fff5f5 0%, #ffe8e8 100%);
        }
        .hf-link-item:hover {
            box-shadow: 0 2px 8px rgba(0,0,0,0.1);
            transform: translateY(-1px);
        }
        .hf-file-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 4px;
        }
        .hf-file-name {
            font-weight: bold;
            color: #2c3e50 !important;
            font-size: 14px;
            display: flex;
            align-items: center;
            gap: 6px;
            flex: 1;
        }
        .hf-file-size {
            font-size: 12px;
            color: #2c3e50 !important;
            background: #e9ecef;
            padding: 2px 6px;
            border-radius: 4px;
            margin-left: 8px;
            font-weight: 500;
        }
        .hf-main-file {
            background: linear-gradient(45deg, #ff6b6b, #feca57);
            color: white;
            padding: 1px 6px;
            border-radius: 8px;
            font-size: 10px;
            font-weight: bold;
        }
        .hf-link-row {
            display: flex;
            align-items: center;
            margin-bottom: 2px;
            padding: 4px;
            background: white;
            border-radius: 4px;
        }
        .hf-link-label {
            font-weight: bold;
            min-width: 45px;
            margin-right: 6px;
            font-size: 11px;
            color: #2c3e50 !important;
        }
        .hf-link-url {
            flex: 1;
            font-family: monospace;
            font-size: 12px;
            word-break: break-all;
            margin-right: 6px;
            color: #2c3e50 !important;
            font-weight: 500;
        }
        .hf-copy-btn {
            padding: 3px 8px;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 10px;
            font-weight: bold;
            transition: all 0.2s ease;
            min-width: 40px;
        }
        .hf-copy-orig { background: #3498db; color: white; }
        .hf-copy-mirror { background: #f39c12; color: white; }
        .hf-copy-btn:hover { opacity: 0.8; }
        .hf-more-info {
            text-align: center;
            padding: 15px;
            color: #7f8c8d;
            font-style: italic;
            background: #ecf0f1;
            border-radius: 8px;
        }
    `;
    document.head.appendChild(style);

    // 创建提取按钮
    const extractBtn = document.createElement('button');
    extractBtn.className = 'hf-extractor-btn';
    extractBtn.innerHTML = '🔗<br>提取';
    document.body.appendChild(extractBtn);

    // 创建模态框
    const modal = document.createElement('div');
    modal.className = 'hf-modal';
    modal.innerHTML = `
        <div class="hf-modal-content">
            <div class="hf-header">
                <h2>🚀 HuggingFace 下载链接提取器</h2>
                <p>同时提供原始链接和镜像链接</p>
            </div>
            <div class="hf-stats" id="hf-stats"></div>
            <div class="hf-buttons">
                <button class="hf-btn hf-btn-close" id="hf-close">❌ 关闭</button>
                <button class="hf-btn hf-btn-copy-first" id="hf-copy-first">📋 复制第一个</button>
                <button class="hf-btn hf-btn-copy-all-orig" id="hf-copy-all-orig">📄 复制全部原始</button>
                <button class="hf-btn hf-btn-copy-all-mirror" id="hf-copy-all-mirror">🚀 复制全部镜像</button>
            </div>
            <div class="hf-search-container">
                <input type="text" class="hf-search-input" id="hf-search-input" placeholder="🔍 输入文件名进行模糊搜索...">
                <button class="hf-search-clear" id="hf-search-clear" title="清除搜索">✕</button>
                <div class="hf-search-stats" id="hf-search-stats"></div>
            </div>
            <div class="hf-sort-buttons">
                <button class="hf-sort-btn active" data-sort="default">🏷️ 默认排序</button>
                <button class="hf-sort-btn" data-sort="name">📝 按名称</button>
                <button class="hf-sort-btn" data-sort="size">📊 按大小</button>
                <button class="hf-sort-btn" data-sort="type">📁 按类型</button>
            </div>
            <div class="hf-links-wrapper">
                <div id="hf-links-container"></div>
            </div>
        </div>
    `;
    document.body.appendChild(modal);

    // 清理文件名,移除查询参数
    function cleanFileName(fileName) {
        return fileName.replace(/\?.*$/, '');
    }

    // 判断是否为主要文件
    function isMainFile(fileName) {
        const mainFilePatterns = [
            /^README\.md$/i,
            /^config\.json$/i,
            /^model\.safetensors$/i,
            /^pytorch_model\.bin$/i,
            /^model\.onnx$/i,
            /^tokenizer\.json$/i,
            /^tokenizer_config\.json$/i,
            /^vocab\.txt$/i,
            /^merges\.txt$/i,
            /\.py$/i,
            /^requirements\.txt$/i,
            /^setup\.py$/i,
            /^__init__\.py$/i,
            /^Dockerfile$/i,
            /^\.dockerignore$/i
        ];

        return mainFilePatterns.some(pattern => pattern.test(fileName));
    }

    // 格式化文件大小
    function formatFileSize(bytes) {
        if (!bytes || bytes === 0) return '未知';
        const sizes = ['B', 'KB', 'MB', 'GB'];
        const i = Math.floor(Math.log(bytes) / Math.log(1024));
        return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i];
    }

    // 从元素中提取文件大小 - 针对HuggingFace页面优化
    function extractFileSize(element) {
        let sizeText = '';
        const fileName = element.getAttribute('download') || element.textContent.trim();

        console.log(`🔍 开始提取文件大小: ${fileName}`);

        // 方法1: 查找HuggingFace特定的文件列表结构
        let currentElement = element;
        for (let level = 0; level < 6; level++) {
            if (!currentElement) break;

            // 在当前层级查找所有文本元素
            const allElements = currentElement.querySelectorAll('*');
            for (const el of allElements) {
                const text = el.textContent.trim();

                // 精确匹配文件大小格式 (如: "459 Bytes", "1 kB", "2.68 kB")
                const sizeMatch = text.match(/^(\d+(?:\.\d+)?)\s*(Bytes?|kB|KB|MB|GB)$/i);
                if (sizeMatch) {
                    sizeText = sizeMatch[0];
                    console.log(`✅ 在层级 ${level} 找到精确文件大小: ${sizeText}`);
                    break;
                }

                // 也匹配包含文件大小的文本
                const sizeInText = text.match(/(\d+(?:\.\d+)?)\s*(Bytes?|kB|KB|MB|GB)\b/i);
                if (sizeInText && text.length < 50) { // 避免匹配到很长的文本
                    sizeText = sizeInText[0];
                    console.log(`✅ 在层级 ${level} 找到文件大小: ${sizeText}`);
                    break;
                }
            }

            if (sizeText) break;
            currentElement = currentElement.parentElement;
        }

        // 方法2: 查找同一行的其他元素
        if (!sizeText) {
            const parentRow = element.closest('li') || element.closest('tr') || element.closest('div');
            if (parentRow) {
                const rowText = parentRow.textContent;
                const sizeMatch = rowText.match(/(\d+(?:\.\d+)?)\s*(Bytes?|kB|KB|MB|GB)\b/i);
                if (sizeMatch) {
                    sizeText = sizeMatch[0];
                    console.log(`✅ 在同一行找到文件大小: ${sizeText}`);
                }
            }
        }

        // 方法3: 全局搜索与文件名相关的大小信息
        if (!sizeText && fileName) {
            console.log(`🔍 全局搜索文件: ${fileName}`);

            // 查找页面中所有可能包含文件大小的元素
            const allTextElements = document.querySelectorAll('span, div, td, li, p');
            for (const el of allTextElements) {
                const text = el.textContent.trim();

                // 检查是否包含文件名和大小信息
                if (text.includes(fileName) || el.closest('*').textContent.includes(fileName)) {
                    const sizeMatch = text.match(/(\d+(?:\.\d+)?)\s*(Bytes?|kB|KB|MB|GB)\b/i);
                    if (sizeMatch) {
                        sizeText = sizeMatch[0];
                        console.log(`✅ 全局搜索找到文件大小: ${sizeText}`);
                        break;
                    }
                }
            }
        }

        // 方法4: 查找相邻元素
        if (!sizeText && element.parentElement) {
            const siblings = Array.from(element.parentElement.children);
            for (const sibling of siblings) {
                const text = sibling.textContent.trim();
                const sizeMatch = text.match(/^(\d+(?:\.\d+)?)\s*(Bytes?|kB|KB|MB|GB)$/i);
                if (sizeMatch) {
                    sizeText = sizeMatch[0];
                    console.log(`✅ 在兄弟元素找到文件大小: ${sizeText}`);
                    break;
                }
            }
        }

        const result = sizeText || '未知';
        console.log(`📊 文件 ${fileName} 最终大小: ${result}`);
        return result;
    }

    // 解析文件大小为字节数(用于排序)
    function parseSizeToBytes(sizeStr) {
        if (!sizeStr || sizeStr === '未知') return 0;

        // 支持更多格式的匹配
        const match = sizeStr.match(/(\d+(?:\.\d+)?)\s*(B|KB|MB|GB|kB|bytes|Bytes)\b/i);
        if (!match) return 0;

        const value = parseFloat(match[1]);
        const unit = match[2].toUpperCase();

        const multipliers = {
            'B': 1,
            'BYTES': 1,
            'KB': 1024,
            'kB': 1024,  // 小写k
            'MB': 1024 * 1024,
            'GB': 1024 * 1024 * 1024
        };

        return value * (multipliers[unit] || 1);
    }

    // 全局变量存储当前排序方式和搜索状态
    let currentSortType = 'default';
    let allLinks = [];
    let filteredLinks = [];
    let currentSearchTerm = '';

    // 提取链接函数
    function extractLinks() {
        const links = [];
        const elements = document.querySelectorAll('a[download][href]');

        console.log(`🔍 找到 ${elements.length} 个下载链接`);

        elements.forEach((element, index) => {
            const href = element.getAttribute('href');
            if (href) {
                const originalLink = href.startsWith('http') ? href : 'https://huggingface.co' + href;
                const mirrorLink = originalLink.replace('huggingface.co', 'hf-mirror.com');
                const rawFileName = href.split('/').pop() || 'unknown';
                const fileName = cleanFileName(rawFileName);
                const isMain = isMainFile(fileName);
                const fileSize = extractFileSize(element);
                const fileSizeBytes = parseSizeToBytes(fileSize);

                console.log(`📁 文件 ${index + 1}: ${fileName}, 大小: ${fileSize}, 字节: ${fileSizeBytes}`);

                links.push({
                    original: originalLink,
                    mirror: mirrorLink,
                    fileName: fileName,
                    isMainFile: isMain,
                    fileSize: fileSize,
                    fileSizeBytes: fileSizeBytes
                });
            }
        });

        allLinks = links;
        filteredLinks = links;
        return sortLinks(links, 'default');
    }

    // 排序函数
    function sortLinks(links, sortType) {
        const sorted = [...links];

        switch (sortType) {
            case 'name':
                return sorted.sort((a, b) => a.fileName.localeCompare(b.fileName));
            case 'size':
                return sorted.sort((a, b) => b.fileSizeBytes - a.fileSizeBytes);
            case 'type':
                return sorted.sort((a, b) => {
                    const extA = a.fileName.split('.').pop().toLowerCase();
                    const extB = b.fileName.split('.').pop().toLowerCase();
                    return extA.localeCompare(extB);
                });
            case 'default':
            default:
                return sorted.sort((a, b) => {
                    if (a.isMainFile && !b.isMainFile) return -1;
                    if (!a.isMainFile && b.isMainFile) return 1;
                    return a.fileName.localeCompare(b.fileName);
                });
        }
    }

    // 模糊搜索功能
    function fuzzySearch(links, searchTerm) {
        if (!searchTerm.trim()) {
            return links;
        }

        const term = searchTerm.toLowerCase().trim();

        return links.filter(link => {
            const fileName = link.fileName.toLowerCase();

            // 精确匹配
            if (fileName.includes(term)) {
                return true;
            }

            // 模糊匹配:检查搜索词的每个字符是否按顺序出现在文件名中
            let termIndex = 0;
            for (let i = 0; i < fileName.length && termIndex < term.length; i++) {
                if (fileName[i] === term[termIndex]) {
                    termIndex++;
                }
            }

            return termIndex === term.length;
        });
    }

    // 高亮搜索结果 - 使用更温和的高亮方式
    function highlightSearchTerm(text, searchTerm) {
        if (!searchTerm.trim()) {
            return text;
        }

        const term = searchTerm.trim().toLowerCase();
        const lowerText = text.toLowerCase();

        // 只进行精确匹配高亮,避免模糊匹配造成的间距问题
        const exactIndex = lowerText.indexOf(term);
        if (exactIndex !== -1) {
            const before = text.substring(0, exactIndex);
            const match = text.substring(exactIndex, exactIndex + term.length);
            const after = text.substring(exactIndex + term.length);
            return before + '<span class="hf-highlight">' + match + '</span>' + after;
        }

        // 如果没有精确匹配,就不高亮,保持原始文本
        return text;
    }

    // 更新搜索统计
    function updateSearchStats(filteredCount, totalCount, searchTerm) {
        const statsElement = document.getElementById('hf-search-stats');
        if (searchTerm.trim()) {
            statsElement.textContent = `找到 ${filteredCount} / ${totalCount} 个文件`;
            statsElement.style.display = 'block';
        } else {
            statsElement.style.display = 'none';
        }
    }

    // 执行搜索和显示
    function performSearch() {
        const searchTerm = document.getElementById('hf-search-input').value;
        currentSearchTerm = searchTerm;

        // 先搜索,再排序
        filteredLinks = fuzzySearch(allLinks, searchTerm);
        const sortedLinks = sortLinks(filteredLinks, currentSortType);

        // 更新统计信息
        updateSearchStats(filteredLinks.length, allLinks.length, searchTerm);

        // 显示结果
        displayLinks(sortedLinks, searchTerm);
    }

    // 复制到剪贴板
    function copyToClipboard(text, button) {
        navigator.clipboard.writeText(text).then(() => {
            const originalText = button.textContent;
            button.textContent = '✅ 已复制!';
            button.style.background = '#27ae60';
            setTimeout(() => {
                button.textContent = originalText;
                button.style.background = '';
            }, 1500);
        }).catch(err => {
            console.error('复制失败:', err);
            alert('复制失败,请手动复制');
        });
    }

    // 显示链接
    function displayLinks(links, searchTerm = '') {
        const container = document.getElementById('hf-links-container');
        const stats = document.getElementById('hf-stats');

        const mainFileCount = links.filter(link => link.isMainFile).length;
        const totalSize = links.reduce((sum, link) => sum + link.fileSizeBytes, 0);
        const totalSizeStr = formatFileSize(totalSize);

        const statsText = mainFileCount > 0
            ? `📊 共 <strong>${links.length}</strong> 个文件,其中 <strong>${mainFileCount}</strong> 个主要文件,总大小: <strong>${totalSizeStr}</strong>`
            : `📊 共 <strong>${links.length}</strong> 个文件,总大小: <strong>${totalSizeStr}</strong>`;

        stats.innerHTML = statsText;

        if (links.length === 0) {
            container.innerHTML = '<div class="hf-more-info">❌ 未找到任何下载链接</div>';
            return;
        }

        let html = '';

        for (let i = 0; i < links.length; i++) {
            const link = links[i];
            const mainFileClass = link.isMainFile ? ' main-file' : '';
            const fileIcon = link.isMainFile ? '⭐' : '📁';
            const mainFileTag = link.isMainFile ? '<span class="hf-main-file">主要</span>' : '';
            const highlightedFileName = highlightSearchTerm(link.fileName, searchTerm);

            html += `
                <div class="hf-link-item${mainFileClass}">
                    <div class="hf-file-header">
                        <div class="hf-file-name">
                            ${fileIcon} ${highlightedFileName}
                            ${mainFileTag}
                        </div>
                        <div class="hf-file-size">${link.fileSize}</div>
                    </div>
                    <div class="hf-link-row">
                        <span class="hf-link-label" style="color: #3498db;">🔗</span>
                        <span class="hf-link-url">${link.original}</span>
                        <button class="hf-copy-btn hf-copy-orig" onclick="copyToClipboard('${link.original}', this)">复制</button>
                    </div>
                    <div class="hf-link-row">
                        <span class="hf-link-label" style="color: #f39c12;">🚀</span>
                        <span class="hf-link-url">${link.mirror}</span>
                        <button class="hf-copy-btn hf-copy-mirror" onclick="copyToClipboard('${link.mirror}', this)">复制</button>
                    </div>
                </div>
            `;
        }

        container.innerHTML = html;
    }

    // 事件监听
    extractBtn.addEventListener('click', () => {
        const links = extractLinks();
        displayLinks(links);
        modal.style.display = 'flex';

        // 更新按钮事件
        document.getElementById('hf-close').onclick = () => {
            modal.style.display = 'none';
        };

        document.getElementById('hf-copy-first').onclick = () => {
            if (filteredLinks.length > 0) {
                const currentLinks = sortLinks(filteredLinks, currentSortType);
                copyToClipboard(currentLinks[0].original, document.getElementById('hf-copy-first'));
            }
        };

        document.getElementById('hf-copy-all-orig').onclick = () => {
            const currentLinks = currentSearchTerm ? filteredLinks : allLinks;
            const allOriginal = currentLinks.map(link => link.original).join('\n');
            copyToClipboard(allOriginal, document.getElementById('hf-copy-all-orig'));
        };

        document.getElementById('hf-copy-all-mirror').onclick = () => {
            const currentLinks = currentSearchTerm ? filteredLinks : allLinks;
            const allMirror = currentLinks.map(link => link.mirror).join('\n');
            copyToClipboard(allMirror, document.getElementById('hf-copy-all-mirror'));
        };

        // 排序按钮事件
        document.querySelectorAll('.hf-sort-btn').forEach(btn => {
            btn.onclick = () => {
                const sortType = btn.getAttribute('data-sort');
                currentSortType = sortType;

                // 更新按钮状态
                document.querySelectorAll('.hf-sort-btn').forEach(b => b.classList.remove('active'));
                btn.classList.add('active');

                // 重新排序并显示
                const sortedLinks = sortLinks(filteredLinks, sortType);
                displayLinks(sortedLinks, currentSearchTerm);
            };
        });

        // 搜索功能事件监听
        const searchInput = document.getElementById('hf-search-input');
        const searchClear = document.getElementById('hf-search-clear');

        // 实时搜索
        searchInput.addEventListener('input', performSearch);

        // 清除搜索
        searchClear.addEventListener('click', () => {
            searchInput.value = '';
            currentSearchTerm = '';
            filteredLinks = allLinks;
            updateSearchStats(0, 0, '');
            const sortedLinks = sortLinks(filteredLinks, currentSortType);
            displayLinks(sortedLinks);
            searchInput.focus();
        });

        // 键盘快捷键
        searchInput.addEventListener('keydown', (e) => {
            if (e.key === 'Escape') {
                searchInput.value = '';
                currentSearchTerm = '';
                filteredLinks = allLinks;
                updateSearchStats(0, 0, '');
                const sortedLinks = sortLinks(filteredLinks, currentSortType);
                displayLinks(sortedLinks);
            }
        });
    });

    // 点击模态框外部关闭
    modal.addEventListener('click', (e) => {
        if (e.target === modal) {
            modal.style.display = 'none';
        }
    });

    // 全局函数供内联事件使用
    window.copyToClipboard = copyToClipboard;

    // 测试文件大小提取功能
    window.testFileSizeExtraction = function() {
        console.log('🧪 开始测试文件大小提取...');
        const downloadLinks = document.querySelectorAll('a[download][href]');
        console.log(`找到 ${downloadLinks.length} 个下载链接`);

        downloadLinks.forEach((link, index) => {
            const fileName = link.getAttribute('download') || link.textContent.trim();
            const fileSize = extractFileSize(link);
            console.log(`${index + 1}. ${fileName} -> ${fileSize}`);
        });

        console.log('🧪 测试完成!');
    };

    console.log('🚀 HuggingFace镜像链接提取器v1.3.3已加载');
    console.log('💡 在控制台运行 testFileSizeExtraction() 来测试文件大小提取');
    console.log('🔍 新功能:支持模糊搜索,可以快速过滤文件列表');
    console.log('🌙 优化:修复黑暗模式下的字体可读性问题');
    console.log('✨ 修复:彻底解决搜索高亮影响文件名间距的问题');
})();