Greasy Fork

Greasy Fork is available in English.

HuggingFace镜像链接提取器

在HuggingFace页面提取下载链接,同时显示原始链接和hf-mirror.com镜像链接。v1.1: 清理文件名、识别主要文件、优化布局

当前为 2025-07-06 提交的版本,查看 最新版本

// ==UserScript==
// @name         HuggingFace镜像链接提取器
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  在HuggingFace页面提取下载链接,同时显示原始链接和hf-mirror.com镜像链接。v1.1: 清理文件名、识别主要文件、优化布局
// @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: 25px;
            max-width: 90vw;
            max-height: 90vh;
            overflow: auto;
            box-shadow: 0 10px 30px rgba(0,0,0,0.3);
            min-width: 700px;
        }
        .hf-header {
            text-align: center;
            margin-bottom: 20px;
            color: #333;
            border-bottom: 2px solid #eee;
            padding-bottom: 15px;
        }
        .hf-stats {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            padding: 15px;
            border-radius: 10px;
            text-align: center;
            margin-bottom: 20px;
            font-weight: bold;
        }
        .hf-buttons {
            display: flex;
            gap: 10px;
            margin-bottom: 20px;
            flex-wrap: wrap;
        }
        .hf-btn {
            padding: 10px 15px;
            border: none;
            border-radius: 8px;
            cursor: pointer;
            font-weight: bold;
            transition: all 0.3s ease;
            flex: 1;
            min-width: 120px;
        }
        .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: 10px;
            padding: 12px;
            margin-bottom: 12px;
            transition: all 0.3s 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 4px 15px rgba(0,0,0,0.1);
            transform: translateY(-2px);
        }
        .hf-file-name {
            font-weight: bold;
            color: #2c3e50;
            margin-bottom: 6px;
            font-size: 16px;
            display: flex;
            align-items: center;
            gap: 8px;
        }
        .hf-main-file {
            background: linear-gradient(45deg, #ff6b6b, #feca57);
            color: white;
            padding: 2px 8px;
            border-radius: 12px;
            font-size: 11px;
            font-weight: bold;
        }
        .hf-link-row {
            display: flex;
            align-items: center;
            margin-bottom: 4px;
            padding: 6px;
            background: white;
            border-radius: 5px;
        }
        .hf-link-label {
            font-weight: bold;
            min-width: 60px;
            margin-right: 8px;
        }
        .hf-link-url {
            flex: 1;
            font-family: monospace;
            font-size: 12px;
            word-break: break-all;
            margin-right: 8px;
        }
        .hf-copy-btn {
            padding: 5px 10px;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            font-size: 11px;
            font-weight: bold;
            transition: all 0.2s ease;
        }
        .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 id="hf-links-container"></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
        ];

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

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

        elements.forEach(element => {
            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);

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

        // 将主要文件排在前面
        return links.sort((a, b) => {
            if (a.isMainFile && !b.isMainFile) return -1;
            if (!a.isMainFile && b.isMainFile) return 1;
            return a.fileName.localeCompare(b.fileName);
        });
    }

    // 复制到剪贴板
    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) {
        const container = document.getElementById('hf-links-container');
        const stats = document.getElementById('hf-stats');

        const mainFileCount = links.filter(link => link.isMainFile).length;
        const statsText = mainFileCount > 0
            ? `📊 共找到 <strong>${links.length}</strong> 个下载链接,其中 <strong>${mainFileCount}</strong> 个主要文件`
            : `📊 共找到 <strong>${links.length}</strong> 个下载链接`;

        stats.innerHTML = statsText;

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

        const displayCount = Math.min(links.length, 5);
        let html = '';

        for (let i = 0; i < displayCount; 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>' : '';

            html += `
                <div class="hf-link-item${mainFileClass}">
                    <div class="hf-file-name">
                        ${fileIcon} 文件 ${i + 1}: ${link.fileName}
                        ${mainFileTag}
                    </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>
            `;
        }

        if (links.length > 5) {
            html += `<div class="hf-more-info">📝 还有 ${links.length - 5} 个链接未显示,请使用上方按钮复制全部链接</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 (links.length > 0) {
                copyToClipboard(links[0].original, document.getElementById('hf-copy-first'));
            }
        };

        document.getElementById('hf-copy-all-orig').onclick = () => {
            const allOriginal = links.map(link => link.original).join('\n');
            copyToClipboard(allOriginal, document.getElementById('hf-copy-all-orig'));
        };

        document.getElementById('hf-copy-all-mirror').onclick = () => {
            const allMirror = links.map(link => link.mirror).join('\n');
            copyToClipboard(allMirror, document.getElementById('hf-copy-all-mirror'));
        };
    });

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

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

    console.log('🚀 HuggingFace镜像链接提取器已加载');
})();