Greasy Fork

Greasy Fork is available in English.

资源嗅探器 Pro v4

强大的网页资源嗅探工具,支持自动检测、分类展示、预览和下载各类网页资源

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

// ==UserScript==
// @name         资源嗅探器 Pro v4
// @namespace    http://tampermonkey.net/
// @version      4.0
// @description  强大的网页资源嗅探工具,支持自动检测、分类展示、预览和下载各类网页资源
// @author       CodeBuddy
// @match        *://*/*
// @grant        GM_download
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// @run-at       document-start
// ==/UserScript==

class ResourceSniffer {
    constructor() {
        // 配置选项
        this.config = {
            // 支持的资源类型
            resourceTypes: {
                image: { enabled: true, extensions: ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg', 'tiff'], icon: '📷' },
                video: { enabled: true, extensions: ['mp4', 'webm', 'avi', 'mov', 'flv', 'wmv', 'mkv'], icon: '🎬' },
                audio: { enabled: true, extensions: ['mp3', 'wav', 'flac', 'aac', 'ogg', 'wma'], icon: '🎵' },
                document: { enabled: true, extensions: ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'csv'], icon: '📄' },
                other: { enabled: true, icon: '📦' }
            },
            // 面板位置和大小
            panel: {
                width: '350px',
                height: '600px',
                left: '20px',
                top: '100px',
                opacity: 0.95
            },
            // 其他配置
            maxResources: 500,
            ignoreSmallResources: true,
            minResourceSize: 1024, // 1KB
            updateInterval: 5000 // 5秒更新一次UI
        };

        // 全局变量
        this.resources = new Map(); // 存储嗅探到的资源
        this.panelVisible = false; // 面板可见性
        this.activeTab = 'all'; // 当前激活的标签
        this.panelElement = null; // 面板元素
        this.toggleButton = null; // 切换按钮
        this.resourceCount = 0; // 资源计数
        this.isDragging = false; // 是否正在拖拽
        this.dragOffset = { x: 0, y: 0 }; // 拖拽偏移量
        this.previewModal = null; // 预览模态框
        this.lastUpdateTime = 0; // 上次更新时间

        // 初始化
        this.init();
    }

    // 初始化函数
    init() {
        // 确保文档就绪后初始化
        this.checkDocumentReady();

        // 拦截请求以嗅探资源
        this.interceptRequests();

        // 监听页面上的媒体元素
        this.monitorMediaElements();
    }

    // 检查文档是否就绪
    checkDocumentReady() {
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', () => this.onDocumentReady());
        } else {
            // 延迟一点执行,确保body已完全加载
            setTimeout(() => this.onDocumentReady(), 300);
        }
    }

    // 文档就绪后执行
    onDocumentReady() {
        console.log('资源嗅探器 Pro v4 已加载');

        // 创建悬浮按钮
        this.createToggleButton();

        // 创建样式
        this.injectStyles();

        // 定期更新UI
        setInterval(() => this.updateUI(), this.config.updateInterval);
    }

    // 注入样式
    injectStyles() {
        GM_addStyle(`
            /* 面板样式 */
            #resource-sniffer-panel {
                position: fixed;
                width: ${this.config.panel.width};
                height: ${this.config.panel.height};
                left: ${this.config.panel.left};
                top: ${this.config.panel.top};
                background: #1e1e1e;
                border-radius: 8px;
                box-shadow: 0 0 15px rgba(0, 0, 0, 0.5);
                z-index: 30000;
                display: flex;
                flex-direction: column;
                opacity: ${this.config.panel.opacity};
                transition: opacity 0.3s;
                font-family: 'Microsoft YaHei', Arial, sans-serif;
            }

            /* 面板头部 */
            #sniffer-panel-header {
                padding: 10px 15px;
                background: #2d2d2d;
                border-top-left-radius: 8px;
                border-top-right-radius: 8px;
                display: flex;
                justify-content: space-between;
                align-items: center;
                cursor: move;
            }

            #panel-title {
                color: white;
                font-size: 14px;
                font-weight: bold;
            }

            #panel-controls {
                display: flex;
                gap: 8px;
            }

            .panel-btn {
                background: none;
                border: none;
                color: white;
                cursor: pointer;
                width: 24px;
                height: 24px;
                display: flex;
                justify-content: center;
                align-items: center;
                border-radius: 4px;
                transition: background 0.2s;
            }

            .panel-btn:hover {
                background: rgba(255, 255, 255, 0.1);
            }

            /* 标签栏 */
            #sniffer-tabs {
                display: flex;
                background: #252526;
                overflow-x: auto;
                white-space: nowrap;
                border-bottom: 1px solid #373737;
            }

            .tab-btn {
                padding: 8px 15px;
                color: #d4d4d4;
                background: none;
                border: none;
                cursor: pointer;
                font-size: 12px;
                transition: all 0.2s;
                display: flex;
                align-items: center;
                gap: 5px;
            }

            .tab-btn.active {
                color: white;
                background: #1e1e1e;
                border-bottom: 2px solid #0078d7;
            }

            .tab-btn:hover:not(.active) {
                background: rgba(255, 255, 255, 0.05);
            }

            /* 资源列表 */
            #resources-container {
                flex: 1;
                overflow-y: auto;
                padding: 10px;
            }

            #resources-list {
                list-style: none;
                padding: 0;
                margin: 0;
            }

            .resource-item {
                background: #2d2d2d;
                border-radius: 6px;
                margin-bottom: 10px;
                overflow: hidden;
                transition: transform 0.2s, box-shadow 0.2s;
            }

            .resource-item:hover {
                transform: translateY(-2px);
                box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
            }

            .resource-header {
                padding: 8px 12px;
                display: flex;
                justify-content: space-between;
                align-items: center;
                background: #252526;
                cursor: pointer;
            }

            .resource-title {
                color: white;
                font-size: 13px;
                overflow: hidden;
                text-overflow: ellipsis;
                white-space: nowrap;
                flex: 1;
                margin-right: 10px;
            }

            .resource-size {
                color: #999;
                font-size: 12px;
                margin-right: 10px;
            }

            .resource-type-badge {
                padding: 2px 6px;
                border-radius: 4px;
                font-size: 11px;
                color: white;
                display: flex;
                align-items: center;
                gap: 3px;
            }

            .type-image {
                background: #0078d7;
            }

            .type-video {
                background: #00bcf2;
            }

            .type-audio {
                background: #7c7cd9;
            }

            .type-document {
                background: #d83b01;
            }

            .type-other {
                background: #515151;
            }

            .resource-content {
                padding: 10px;
                display: none;
            }

            .resource-preview-container {
                width: 100%;
                height: 180px;
                background: #1e1e1e;
                border-radius: 4px;
                margin-bottom: 10px;
                display: flex;
                justify-content: center;
                align-items: center;
                overflow: hidden;
                position: relative;
            }

            .resource-thumbnail {
                width: 100%;
                height: 100%;
                object-fit: contain;
            }

            .resource-actions {
                display: flex;
                gap: 10px;
            }

            .resource-btn {
                flex: 1;
                padding: 8px 12px;
                border: none;
                border-radius: 4px;
                cursor: pointer;
                font-size: 13px;
                font-weight: bold;
                transition: background 0.2s;
            }

            .preview {
                background: #0078d7;
                color: white;
            }

            .preview:hover {
                background: #005a9e;
            }

            .download {
                background: #00b42a;
                color: white;
            }

            .download:hover {
                background: #008c22;
            }

            .resource-url {
                margin-top: 10px;
                padding: 8px;
                background: #1e1e1e;
                border-radius: 4px;
                font-size: 12px;
                color: #999;
                overflow: hidden;
                text-overflow: ellipsis;
                white-space: nowrap;
            }

            /* 空状态 */
            #empty-state {
                display: flex;
                flex-direction: column;
                justify-content: center;
                align-items: center;
                height: 100%;
                color: #666;
                text-align: center;
            }

            #empty-state svg {
                width: 64px;
                height: 64px;
                margin-bottom: 15px;
                opacity: 0.3;
            }

            /* 预览模态框 */
            #preview-modal {
                position: fixed;
                top: 0;
                left: 0;
                width: 100%;
                height: 100%;
                background: rgba(0, 0, 0, 0.8);
                display: none;
                justify-content: center;
                align-items: center;
                z-index: 30000;
                flex-direction: column;
            }

            #preview-modal .modal-content {
                background: #1e1e1e;
                border-radius: 8px;
                max-width: 90%;
                max-height: 90%;
                overflow: hidden;
                position: relative;
            }

            #preview-modal .modal-header {
                padding: 10px 15px;
                background: #2d2d2d;
                color: white;
                display: flex;
                justify-content: space-between;
                align-items: center;
            }

            #preview-modal .preview-title {
                font-size: 14px;
                font-weight: bold;
            }

            #preview-modal .close-btn {
                background: none;
                border: none;
                color: white;
                font-size: 18px;
                cursor: pointer;
            }

            #preview-modal .preview-body {
                padding: 10px;
                max-height: 70vh;
                overflow: auto;
                display: flex;
                justify-content: center;
                align-items: center;
            }

            #preview-modal img, #preview-modal video, #preview-modal audio {
                max-width: 100%;
                max-height: 70vh;
            }

            /* 切换按钮 */
            #resource-sniffer-toggle {
                position: fixed;
                width: 50px;
                height: 50px;
                border-radius: 50%;
                background: linear-gradient(135deg, #0078d7, #00bcf2);
                color: white;
                border: none;
                cursor: pointer;
                font-size: 20px;
                z-index: 29999;
                box-shadow: 0 5px 15px rgba(0, 120, 215, 0.3);
                display: flex;
                justify-content: center;
                align-items: center;
                right: 20px;
                bottom: 20px;
                transition: all 0.3s;
            }

            #resource-sniffer-toggle:hover {
                transform: scale(1.1);
                box-shadow: 0 8px 20px rgba(0, 120, 215, 0.4);
            }

            #resource-sniffer-toggle .resource-count {
                position: absolute;
                top: -5px;
                right: -5px;
                background: #ff3b30;
                color: white;
                border-radius: 50%;
                width: 20px;
                height: 20px;
                font-size: 12px;
                display: flex;
                justify-content: center;
                align-items: center;
                font-weight: bold;
                border: 2px solid white;
            }

            /* 滚动条样式 */
            ::-webkit-scrollbar {
                width: 8px;
                height: 8px;
            }

            ::-webkit-scrollbar-track {
                background: #2d2d2d;
            }

            ::-webkit-scrollbar-thumb {
                background: #555;
                border-radius: 4px;
            }

            ::-webkit-scrollbar-thumb:hover {
                background: #777;
            }
        `);
    }

    // 创建切换按钮
    createToggleButton() {
        if (!document.body) {
            console.error('document.body 不存在,无法创建切换按钮');
            return;
        }

        // 避免重复创建
        if (this.toggleButton) {
            return;
        }

        try {
            this.toggleButton = document.createElement('button');
            this.toggleButton.id = 'resource-sniffer-toggle';
            this.toggleButton.innerHTML = `
                🕵️
                <span class="resource-count">0</span>
            `;
            this.toggleButton.title = '资源嗅探器 Pro v4';

            // 添加点击事件
            this.toggleButton.addEventListener('click', () => this.togglePanel());

            // 添加到页面
            document.body.appendChild(this.toggleButton);
            console.log('切换按钮已创建');
        } catch (error) {
            console.error('创建切换按钮失败:', error);
            // 尝试延迟后重试
            setTimeout(() => this.createToggleButton(), 500);
        }
    }

    // 切换面板显示/隐藏
    togglePanel() {
        this.panelVisible = !this.panelVisible;

        if (this.panelVisible) {
            this.createPanel();
            this.panelElement.style.display = 'flex';
        } else {
            if (this.panelElement) {
                this.panelElement.style.display = 'none';
            }
        }
    }

    // 创建面板
    createPanel() {
        if (this.panelElement) {
            return;
        }

        // 确保document.body已加载
        if (!document.body) {
            console.error('document.body 不存在,无法创建面板');
            setTimeout(() => this.createPanel(), 500);
            return;
        }

        try {
            // 创建面板元素
            this.panelElement = document.createElement('div');
            this.panelElement.id = 'resource-sniffer-panel';
            this.panelElement.style.display = 'none';

            // 面板头部
            const header = document.createElement('div');
            header.id = 'sniffer-panel-header';
            header.innerHTML = `
                <div id="panel-title">资源嗅探器 Pro v4</div>
                <div id="panel-controls">
                    <button class="panel-btn" id="minimize-btn" title="最小化">—</button>
                    <button class="panel-btn" id="refresh-btn" title="刷新">↻</button>
                    <button class="panel-btn" id="close-btn" title="关闭">×</button>
                </div>
            `;

            // 标签栏
            const tabs = document.createElement('div');
            tabs.id = 'sniffer-tabs';

            // 添加所有标签
            let tabsHTML = '<button class="tab-btn active" data-tab="all">全部</button>';
            for (const [type, config] of Object.entries(this.config.resourceTypes)) {
                if (config.enabled) {
                    tabsHTML += `<button class="tab-btn" data-tab="${type}">${config.icon} ${type}</button>`;
                }
            }
            tabs.innerHTML = tabsHTML;

            // 资源列表容器
            const resourcesContainer = document.createElement('div');
            resourcesContainer.id = 'resources-container';

            // 空状态
            const emptyState = document.createElement('div');
            emptyState.id = 'empty-state';
            emptyState.innerHTML = `
                <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                    <circle cx="12" cy="12" r="10"></circle>
                    <line x1="2" y1="12" x2="22" y2="12"></line>
                    <path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path>
                </svg>
                <div>暂无检测到的资源</div>
                <div style="font-size: 12px; margin-top: 5px;">访问网页时会自动检测资源</div>
            `;
            resourcesContainer.appendChild(emptyState);

            // 资源列表
            const resourcesList = document.createElement('ul');
            resourcesList.id = 'resources-list';
            resourcesContainer.appendChild(resourcesList);

            // 组装面板
            this.panelElement.appendChild(header);
            this.panelElement.appendChild(tabs);
            this.panelElement.appendChild(resourcesContainer);

            // 添加到页面
            document.body.appendChild(this.panelElement);

            // 添加标签点击事件
            document.querySelectorAll('.tab-btn').forEach(btn => {
                btn.addEventListener('click', () => {
                    // 移除所有激活状态
                    document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
                    // 添加当前激活状态
                    btn.classList.add('active');
                    // 更新当前标签
                    this.activeTab = btn.dataset.tab;
                    // 更新资源列表
                    this.updateResourceList();
                });
            });

            // 添加面板控制事件
            document.getElementById('close-btn').addEventListener('click', () => {
                this.panelVisible = false;
                this.panelElement.style.display = 'none';
            });

            document.getElementById('minimize-btn').addEventListener('click', () => {
                this.panelElement.style.height = '40px';
                tabs.style.display = 'none';
                resourcesContainer.style.display = 'none';
            });

            document.getElementById('refresh-btn').addEventListener('click', () => {
                this.updateResourceList();
            });

            // 添加拖拽功能
            header.addEventListener('mousedown', (e) => {
                this.isDragging = true;
                this.dragOffset.x = e.clientX - this.panelElement.getBoundingClientRect().left;
                this.dragOffset.y = e.clientY - this.panelElement.getBoundingClientRect().top;
            });

            document.addEventListener('mousemove', (e) => {
                if (!this.isDragging) return;
                e.preventDefault();
                const x = e.clientX - this.dragOffset.x;
                const y = e.clientY - this.dragOffset.y;
                this.panelElement.style.left = `${x}px`;
                this.panelElement.style.top = `${y}px`;
            });

            document.addEventListener('mouseup', () => {
                this.isDragging = false;
            });

            console.log('面板已创建');
        } catch (error) {
            console.error('创建面板失败:', error);
            // 尝试延迟后重试
            setTimeout(() => this.createPanel(), 500);
        }
    }

    // 更新UI
    updateUI() {
        const now = Date.now();
        if (now - this.lastUpdateTime < this.config.updateInterval) {
            return;
        }
        this.lastUpdateTime = now;

        // 更新资源计数
        this.updateResourceCount();

        // 如果面板可见,更新资源列表
        if (this.panelVisible) {
            this.updateResourceList();
        }
    }

    // 更新资源计数
    updateResourceCount() {
        const count = this.resources.size;
        this.resourceCount = count;

        // 更新按钮上的计数
        if (this.toggleButton) {
            const countElement = this.toggleButton.querySelector('.resource-count');
            if (countElement) {
                countElement.textContent = count;
            }
        }
    }

    // 更新资源列表
    updateResourceList() {
        const resourcesList = document.getElementById('resources-list');
        const emptyState = document.getElementById('empty-state');

        if (!resourcesList || !emptyState) {
            return;
        }

        // 清空列表
        resourcesList.innerHTML = '';

        // 筛选资源
        let filteredResources = Array.from(this.resources.values());

        if (this.activeTab !== 'all') {
            filteredResources = filteredResources.filter(resource => resource.type === this.activeTab);
        }

        // 按大小排序(从大到小)
        filteredResources.sort((a, b) => b.size - a.size);

        // 显示空状态或资源列表
        if (filteredResources.length === 0) {
            emptyState.style.display = 'flex';
        } else {
            emptyState.style.display = 'none';

            // 添加资源项
            filteredResources.forEach(resource => {
                const resourceItem = this.createResourceItem(resource);
                resourcesList.appendChild(resourceItem);
            });
        }
    }

    // 创建资源项
    createResourceItem(resource) {
        const item = document.createElement('li');
        item.className = 'resource-item';

        // 资源头部
        const header = document.createElement('div');
        header.className = 'resource-header';
        header.innerHTML = `
            <div class="resource-title">${this.truncateText(resource.name, 30)}</div>
            <div class="resource-size">${this.formatSize(resource.size)}</div>
            <div class="resource-type-badge type-${resource.type}">
                ${this.config.resourceTypes[resource.type].icon} ${resource.type}
            </div>
        `;

        // 点击展开/折叠
        header.addEventListener('click', () => {
            const content = item.querySelector('.resource-content');
            if (content) {
                content.style.display = content.style.display === 'block' ? 'none' : 'block';
            }
        });

        // 资源内容
        const content = document.createElement('div');
        content.className = 'resource-content';

        // 资源预览
        const previewContainer = document.createElement('div');
        previewContainer.className = 'resource-preview-container';

        // 根据资源类型创建预览
        if (resource.type === 'image') {
            const img = document.createElement('img');
            img.className = 'resource-thumbnail';
            img.src = resource.url;
            img.alt = resource.name;
            previewContainer.appendChild(img);
        } else if (resource.type === 'video') {
            const video = document.createElement('video');
            video.className = 'resource-thumbnail';
            video.controls = true;
            video.src = resource.url;
            previewContainer.appendChild(video);
        } else if (resource.type === 'audio') {
            const audio = document.createElement('audio');
            audio.controls = true;
            audio.src = resource.url;
            previewContainer.appendChild(audio);
        } else {
            // 其他类型的资源,显示图标
            previewContainer.innerHTML = `
                <div style="text-align: center;">
                    <div style="font-size: 48px;">${this.config.resourceTypes[resource.type].icon}</div>
                    <div style="margin-top: 10px;">${resource.type.toUpperCase()}</div>
                </div>
            `;
        }

        // 资源操作
        const actions = document.createElement('div');
        actions.className = 'resource-actions';
        actions.innerHTML = `
            <button class="resource-btn preview" data-url="${resource.url}" data-type="${resource.type}" data-name="${resource.name}">预览</button>
            <button class="resource-btn download" data-url="${resource.url}" data-name="${resource.name}">下载</button>
        `;

        // 添加操作事件
        actions.querySelector('.preview').addEventListener('click', (e) => {
            e.stopPropagation();
            this.previewResource(resource);
        });

        actions.querySelector('.download').addEventListener('click', (e) => {
            e.stopPropagation();
            this.downloadResource(resource);
        });

        // 资源URL
        const url = document.createElement('div');
        url.className = 'resource-url';
        url.textContent = resource.url;

        // 组装资源项
        content.appendChild(previewContainer);
        content.appendChild(actions);
        content.appendChild(url);

        item.appendChild(header);
        item.appendChild(content);

        return item;
    }

    // 预览资源
    previewResource(resource) {
        // 创建预览模态框
        if (!this.previewModal) {
            this.createPreviewModal();
        }

        // 设置预览内容
        const previewBody = document.querySelector('#preview-modal .preview-body');
        const previewTitle = document.querySelector('#preview-modal .preview-title');

        if (previewBody && previewTitle) {
            previewTitle.textContent = resource.name;

            // 根据资源类型创建预览内容
            if (resource.type === 'image') {
                previewBody.innerHTML = `
                    <img src="${resource.url}" alt="${resource.name}">
                `;
            } else if (resource.type === 'video') {
                previewBody.innerHTML = `
                    <video controls autoplay>
                        <source src="${resource.url}" type="video/${resource.extension}">
                    </video>
                `;
            } else if (resource.type === 'audio') {
                previewBody.innerHTML = `
                    <audio controls autoplay>
                        <source src="${resource.url}" type="audio/${resource.extension}">
                    </audio>
                `;
            } else {
                // 其他类型资源,显示信息和下载按钮
                previewBody.innerHTML = `
                    <div style="text-align: center; color: white;">
                        <div style="font-size: 64px; margin-bottom: 20px;">${this.config.resourceTypes[resource.type].icon}</div>
                        <h3>${resource.name}</h3>
                        <p>类型: ${resource.type}</p>
                        <p>大小: ${this.formatSize(resource.size)}</p>
                        <button class="resource-btn download" style="margin-top: 20px;" data-url="${resource.url}" data-name="${resource.name}">下载</button>
                    </div>
                `;

                // 添加下载事件
                previewBody.querySelector('.download').addEventListener('click', () => {
                    this.downloadResource(resource);
                });
            }
        }

        // 显示预览模态框
        this.previewModal.style.display = 'flex';
    }

    // 创建预览模态框
    createPreviewModal() {
        if (this.previewModal) {
            return;
        }

        this.previewModal = document.createElement('div');
        this.previewModal.id = 'preview-modal';
        this.previewModal.innerHTML = `
            <div class="modal-content">
                <div class="modal-header">
                    <div class="preview-title"></div>
                    <button class="close-btn">&times;</button>
                </div>
                <div class="preview-body"></div>
            </div>
        `;

        // 添加关闭事件
        this.previewModal.querySelector('.close-btn').addEventListener('click', () => {
            this.previewModal.style.display = 'none';
        });

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

        // 添加到页面
        document.body.appendChild(this.previewModal);
    }

    // 下载资源
    downloadResource(resource) {
        try {
            // 使用GM_download下载
            GM_download({
                url: resource.url,
                name: resource.name,
                saveAs: true
            });
            console.log(`正在下载资源: ${resource.name}`);
        } catch (error) {
            console.error(`下载资源失败: ${resource.name}`, error);
            // 降级方案:创建a标签下载
            const a = document.createElement('a');
            a.href = resource.url;
            a.download = resource.name;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
        }
    }

    // 拦截请求
    interceptRequests() {
        // 保存原始fetch和XMLHttpRequest
        const originalFetch = window.fetch;
        const originalXhrOpen = XMLHttpRequest.prototype.open;

        // 重写fetch
        window.fetch = async (url, options) => {
            // 处理请求
            this.handleRequest(url);
            // 执行原始fetch
            return originalFetch.apply(this, arguments);
        };

        // 重写XMLHttpRequest.open
        XMLHttpRequest.prototype.open = function(method, url) {
            // 处理请求
            this._url = url;
            this.addEventListener('load', () => {
                if (this.status >= 200 && this.status < 300) {
                    // 尝试获取响应大小
                    const size = this.getResponseHeader('Content-Length') || 0;
                    this.handleRequest(this._url, parseInt(size));
                }
            });
            // 执行原始open
            originalXhrOpen.apply(this, arguments);
        };
    }

    // 处理请求
    handleRequest(url, size = 0) {
        // 跳过本身的请求
        if (url.includes('resource-sniffer')) {
            return;
        }

        // 针对特定网站的视频URL处理
        const videoUrl = this.extractVideoUrl(url);
        if (videoUrl) {
            // 处理提取到的视频URL
            const resourceInfo = this.getResourceInfo(videoUrl, size);
            if (resourceInfo) {
                // 添加到资源列表
                this.addResource(resourceInfo);
            }
            return;
        }

        // 检查是否为有效的资源URL
        const resourceInfo = this.getResourceInfo(url, size);
        if (resourceInfo) {
            // 添加到资源列表
            this.addResource(resourceInfo);
        }
    }

    // 提取视频URL(针对特定网站)
    extractVideoUrl(url) {
        // 西瓜视频
        if (url.includes('ixigua.com')) {
            // 处理西瓜视频API请求
            if (url.includes('ixigua.com/api/albumv2/') ||
                url.includes('ixigua.com/api/videov2/pseries_more_v2') ||
                url.includes('ixigua.com/api/mixVideo/')) {
                return url;
            }
            // 普通西瓜视频URL
            return url;
        }

        // 抖音
        if (url.includes('douyin.com')) {
            return url;
        }

        // YouTube
        if (url.includes('youtube.com') || url.includes('youtu.be')) {
            return url;
        }

        // 央视网
        if (url.includes('cntv') && url.includes('/asp/')) {
            // 央视网URL特殊处理
            const realUrl = url.replace(/.+?cntv.*?\/asp\/.*?hls\/(.*)/, 'https://hls.cntv.myalicdn.com/asp/hls/$1');
            return realUrl;
        }

        // 检查是否为常见视频格式URL(即使没有扩展名)
        if (url.includes('.m3u8') || url.includes('.mp4') ||
            url.includes('.webm') || url.includes('.flv') ||
            url.includes('.avi') || url.includes('.mov') ||
            url.includes('.f4v') || url.includes('.mkv') ||
            url.includes('.rmvb') || url.includes('.wmv') ||
            url.includes('.3gp')) {
            return url;
        }

        // 处理没有扩展名但可能是视频的URL
        if (url.includes('video') || url.includes('stream') ||
            url.includes('media') || url.includes('play') ||
            url.includes('source') || url.includes('file')) {
            // 检查是否为PHP请求但没有明显视频扩展名
            if (url.includes('.php') && !url.includes('.jpg') && !url.includes('.png') &&
                !url.includes('.gif') && !url.includes('.css') && !url.includes('.js')) {
                return url + '&type=.m3u8'; // 尝试添加m3u8格式参数
            }
            return url;
        }

        return null;
    }

    // 获取资源信息
    getResourceInfo(url, size = 0) {
        try {
            const parsedUrl = new URL(url);
            const pathname = parsedUrl.pathname;
            const filename = pathname.split('/').pop() || 'unknown';
            let extension = filename.split('.').pop().toLowerCase();
            let siteInfo = this.getSiteInfo(url);

            // 确定资源类型
            let type = 'other';

            // 特殊处理没有扩展名但可能是视频的URL
            if (extension === filename) {
                // 检查是否为视频URL
                if (url.includes('.m3u8') || url.includes('video') ||
                    url.includes('stream') || url.includes('media') ||
                    url.includes('play') || url.includes('source')) {
                    type = 'video';
                    extension = 'mp4'; // 假设默认视频格式
                }
            } else {
                // 根据扩展名确定资源类型
                for (const [resourceType, config] of Object.entries(this.config.resourceTypes)) {
                    if (config.extensions && config.extensions.includes(extension)) {
                        type = resourceType;
                        break;
                    }
                }
            }

            // 额外检查:如果URL包含视频相关关键词但类型不是视频
            if (type !== 'video' && (
                url.includes('video') || url.includes('stream') ||
                url.includes('media') || url.includes('.m3u8') ||
                url.includes('play') || url.includes('source'))) {
                type = 'video';
            }

            // 网站特定处理
            let headers = {};
            if (siteInfo === 'ixigua') {
                headers.Referer = 'https://www.ixigua.com/';
            } else if (siteInfo === 'douyin') {
                headers.Referer = 'https://www.douyin.com/';
            } else if (siteInfo === 'cntv') {
                // 央视网特定处理
                if (url.includes('.m3u8')) {
                    // 处理不同分辨率
                    if (url.includes('main.m3u8')) {
                        // 提供多种分辨率选项
                        const url720p = url.replace(/main.m3u8.*/, '1200.m3u8').replace('hls/main/', 'hls/1200/');
                        const url1080p = url.replace(/main.m3u8.*/, '2000.m3u8').replace('hls/main/', 'hls/2000/');
                        // 这里可以返回多个分辨率的视频
                    }
                }
            } else if (siteInfo === 'javplayer') {
                headers.Referer = 'https://javplayer.me/';
            } else if (siteInfo === 'aliyundrive') {
                headers.Referer = 'https://www.aliyundrive.com/';
            }

            // 添加User-Agent以提高兼容性
            headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36';
            headers['Accept-Language'] = 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7';

            // 忽略小资源
            if (this.config.ignoreSmallResources && size > 0 && size < this.config.minResourceSize) {
                // 视频资源即使小也不忽略
                if (type !== 'video') {
                    return null;
                }
            }

            return {
                id: url,
                url: url,
                name: filename,
                extension: extension,
                type: type,
                size: size,
                timestamp: Date.now(),
                // 添加网站特定信息
                site: siteInfo,
                // 添加请求头信息
                headers: headers
            };
        } catch (error) {
            console.error('解析URL失败:', error);
            return null;
        }
    }

    // 获取网站信息
    getSiteInfo(url) {
        if (url.includes('ixigua.com')) return 'ixigua';
        if (url.includes('douyin.com')) return 'douyin';
        if (url.includes('youtube.com') || url.includes('youtu.be')) return 'youtube';
        if (url.includes('bilibili.com')) return 'bilibili';
        if (url.includes('cntv')) return 'cntv';
        if (url.includes('javplayer.me')) return 'javplayer';
        if (url.includes('aliyundrive.com')) return 'aliyundrive';
        if (url.includes('weibo.cn')) return 'weibo';
        return 'other';
    }

    // 添加资源
    addResource(resource) {
        // 检查是否已存在
        if (this.resources.has(resource.id)) {
            return;
        }

        // 检查是否超过最大资源数
        if (this.resources.size >= this.config.maxResources) {
            // 删除最早添加的资源
            const oldestResource = Array.from(this.resources.entries()).sort((a, b) => a[1].timestamp - b[1].timestamp)[0];
            this.resources.delete(oldestResource[0]);
        }

        // 添加资源
        this.resources.set(resource.id, resource);

        // 更新UI
        this.updateUI();
    }

    // 监听媒体元素
    monitorMediaElements() {
        // 定期检查新的媒体元素
        setInterval(() => {
            // 检查图片
            document.querySelectorAll('img:not([data-sniffed])').forEach(img => {
                img.dataset.sniffed = 'true';
                const url = img.src;
                this.handleRequest(url);
            });

            // 检查视频
            document.querySelectorAll('video:not([data-sniffed])').forEach(video => {
                video.dataset.sniffed = 'true';
                // 检查视频源
                video.querySelectorAll('source').forEach(source => {
                    const url = source.src;
                    this.handleRequest(url);
                });
                // 如果视频有直接src
                if (video.src) {
                    this.handleRequest(video.src);
                }
            });

            // 检查音频
            document.querySelectorAll('audio:not([data-sniffed])').forEach(audio => {
                audio.dataset.sniffed = 'true';
                // 检查音频源
                audio.querySelectorAll('source').forEach(source => {
                    const url = source.src;
                    this.handleRequest(url);
                });
                // 如果音频有直接src
                if (audio.src) {
                    this.handleRequest(audio.src);
                }
            });
        }, 2000);
    }

    // 格式化大小
    formatSize(bytes) {
        if (bytes === 0) return '0 Bytes';
        const k = 1024;
        const sizes = ['Bytes', 'KB', 'MB', 'GB'];
        const i = Math.floor(Math.log(bytes) / Math.log(k));
        return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
    }

    // 截断文本
    truncateText(text, maxLength) {
        if (text.length <= maxLength) return text;
        return text.substring(0, maxLength) + '...';
    }
}

// 初始化资源嗅探器
window.addEventListener('load', () => {
    setTimeout(() => {
        const sniffer = new ResourceSniffer();
        // 为了测试,直接显示面板
        sniffer.panelVisible = true;
        sniffer.createPanel();
        sniffer.panelElement.style.display = 'flex';
    }, 1000);
});