您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
强大的网页资源嗅探工具,支持自动检测、分类展示、预览和下载各类网页资源
// ==UserScript== // @name 资源嗅探器 Pro v4 // @namespace http://tampermonkey.net/ // @version 4.1 // @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); } // 创建视频播放器 createVideoPlayer(resource) { // 检查是否已存在播放器 if (document.getElementById('resource-player')) { document.getElementById('resource-player').remove(); } // 创建播放器容器 const playerContainer = document.createElement('div'); playerContainer.id = 'resource-player'; playerContainer.className = 'resource-player'; playerContainer.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 80%; max-width: 1000px; background: rgba(0, 0, 0, 0.9); border-radius: 10px; padding: 10px; z-index: 9999; box-shadow: 0 0 20px rgba(0, 0, 0, 0.5); `; // 创建关闭按钮 const closeBtn = document.createElement('button'); closeBtn.innerText = '关闭'; closeBtn.style.cssText = ` position: absolute; top: 10px; right: 10px; background: rgba(255, 0, 0, 0.7); color: white; border: none; border-radius: 5px; padding: 5px 10px; cursor: pointer; z-index: 10; `; closeBtn.onclick = () => { playerContainer.remove(); }; // 创建视频元素 const videoElement = document.createElement('video'); videoElement.controls = true; videoElement.style.width = '100%'; videoElement.style.height = 'auto'; videoElement.style.borderRadius = '5px'; // 设置视频源 const sourceElement = document.createElement('source'); sourceElement.src = resource.url; sourceElement.type = 'video/mp4'; videoElement.appendChild(sourceElement); // 处理m3u8格式 if (resource.url.includes('.m3u8')) { // 检查是否支持HLS if (window.Hls) { const hls = new Hls(); hls.loadSource(resource.url); hls.attachMedia(videoElement); hls.on(Hls.Events.MANIFEST_PARSED, () => { videoElement.play(); }); hls.on(Hls.Events.ERROR, (event, data) => { console.error('HLS播放错误:', data); }); } else if (videoElement.canPlayType('application/vnd.apple.mpegurl')) { videoElement.src = resource.url; videoElement.addEventListener('loadedmetadata', () => { videoElement.play(); }); } else { alert('您的浏览器不支持HLS播放,请安装HLS插件或使用其他浏览器'); playerContainer.remove(); } } // 添加标题 const titleElement = document.createElement('div'); titleElement.innerText = `正在播放: ${resource.name} ${resource.quality !== 'unknown' ? `(${resource.quality})` : ''}`; titleElement.style.cssText = ` color: white; margin: 10px 0; font-weight: bold; `; // 添加下载按钮 const downloadBtn = document.createElement('button'); downloadBtn.innerText = '下载'; downloadBtn.style.cssText = ` background: rgba(0, 128, 255, 0.7); color: white; border: none; border-radius: 5px; padding: 5px 10px; margin-right: 10px; cursor: pointer; `; downloadBtn.onclick = () => { this.downloadResource(resource); }; // 添加控制栏 const controlBar = document.createElement('div'); controlBar.style.cssText = ` display: flex; justify-content: space-between; align-items: center; margin-top: 10px; `; controlBar.appendChild(downloadBtn); controlBar.appendChild(closeBtn); // 组装播放器 playerContainer.appendChild(titleElement); playerContainer.appendChild(videoElement); playerContainer.appendChild(controlBar); // 添加到页面 document.body.appendChild(playerContainer); // 添加HLS支持脚本(如果需要) if (resource.url.includes('.m3u8') && !window.Hls) { const hlsScript = document.createElement('script'); hlsScript.src = 'https://cdn.jsdelivr.net/npm/hls.js@latest'; document.head.appendChild(hlsScript); hlsScript.onload = () => { this.createVideoPlayer(resource); }; } } // 注入样式 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; } /* 自定义样式类 */ .current-video-badge { background-color: #ff3b30; color: white; padding: 2px 5px; border-radius: 3px; font-size: 10px; margin-left: 5px; } .priority-video { background-color: #ff9500 !important; } /* 手机端响应式布局 */ @media screen and (max-width: 768px) { #resource-sniffer-panel { width: 95% !important; height: 80% !important; left: 2.5% !important; top: 10% !important; } .resource-title { font-size: 14px !important; max-width: 60% !important; } .resource-size { font-size: 12px !important; } .resource-type-badge { font-size: 12px !important; padding: 2px 5px !important; margin-left: 5px !important; } .resource-btn { padding: 6px !important; font-size: 12px !important; } } /* 滚动条样式 */ ::-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); } // 优化排序:视频优先,当前播放视频优先,M3U8/MP4优先,最后按大小排序 const currentVideoUrl = this.getCurrentVideoUrl(); filteredResources.sort((a, b) => { // 1. 判断是否为视频类型 const isVideoA = a.type === 'video' && (a.extension === 'mp4' || a.extension === 'm3u8' || a.url.includes('.mp4') || a.url.includes('.m3u8')); const isVideoB = b.type === 'video' && (b.extension === 'mp4' || b.extension === 'm3u8' || b.url.includes('.mp4') || b.url.includes('.m3u8')); if (isVideoA !== isVideoB) { return isVideoA ? -1 : 1; } // 2. 判断是否为当前播放视频 const isCurrentA = currentVideoUrl && (a.url === currentVideoUrl || a.url.includes(currentVideoUrl.split('?')[0])); const isCurrentB = currentVideoUrl && (b.url === currentVideoUrl || b.url.includes(currentVideoUrl.split('?')[0])); if (isCurrentA !== isCurrentB) { return isCurrentA ? -1 : 1; } // 3. M3U8和MP4优先 const isPriorityFormatA = a.extension === 'm3u8' || a.url.includes('.m3u8') || a.extension === 'mp4' || a.url.includes('.mp4'); const isPriorityFormatB = b.extension === 'm3u8' || b.url.includes('.m3u8') || b.extension === 'mp4' || b.url.includes('.mp4'); if (isPriorityFormatA !== isPriorityFormatB) { return isPriorityFormatA ? -1 : 1; } // 4. 最后按大小排序 return 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 isPriorityVideo = resource.type === 'video' && (resource.extension === 'mp4' || resource.extension === 'm3u8' || resource.url.includes('.mp4') || resource.url.includes('.m3u8')); // 判断是否为当前播放视频 const currentVideoUrl = this.getCurrentVideoUrl(); const isCurrentVideo = currentVideoUrl && (resource.url === currentVideoUrl || resource.url.includes(currentVideoUrl.split('?')[0])); // 资源头部 const header = document.createElement('div'); header.className = 'resource-header'; header.innerHTML = ` <div class="resource-title">${this.truncateText(resource.name, 30)}${isCurrentVideo ? ' <span class="current-video-badge">正在播放</span>' : ''}</div> <div class="resource-size">${this.formatSize(resource.size)}</div> <div class="resource-type-badge type-${resource.type} ${isPriorityVideo ? 'priority-video' : ''}"> ${isPriorityVideo ? '🎬' : this.config.resourceTypes[resource.type].icon} ${resource.type}${isPriorityVideo ? (resource.extension === 'm3u8' || resource.url.includes('.m3u8') ? ' (M3U8)' : ' (MP4)') : ''} </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">×</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 { console.log(`开始下载资源: ${resource.name}`); // 判断资源类型 if (resource.url.includes('.m3u8') || resource.type === 'video') { // 视频资源,使用高级下载方法 this.m3u8Download(resource); } else { // 普通资源下载 GM_download({ url: resource.url, name: resource.name, saveAs: true, headers: resource.headers || {}, onprogress: (event) => { if (event) { const progress = ((event.loaded / event.total) * 100).toFixed(2); console.log(`下载进度: ${progress}% - ${resource.name}`); // 这里可以添加进度显示逻辑 } }, onload: () => { console.log(`资源下载完成: ${resource.name}`); }, onerror: (error) => { console.error(`下载资源失败: ${resource.name}`, error); // 尝试使用备用下载方法 this.fallbackDownload(resource); } }); } } catch (error) { console.error(`下载资源失败: ${resource.name}`, error); this.fallbackDownload(resource); } } // 降级下载方案 fallbackDownload(resource) { const a = document.createElement('a'); a.href = resource.url; a.download = resource.name; // 添加请求头信息 if (resource.headers) { // 注意: 浏览器环境下无法通过a标签设置请求头 console.warn('无法为a标签下载设置请求头,可能导致下载失败'); } document.body.appendChild(a); a.click(); document.body.removeChild(a); } // m3u8视频下载 m3u8Download(resource) { console.log(`开始解析m3u8视频: ${resource.name}`); // 创建下载进度条 this.createDownloadProgress(resource.id, resource.name); // 初始化变量 const chunks = []; const chunkSize = 5 * 1024 * 1024; // 5MB per chunk let totalSize = 0; let downloadedSize = 0; let isEncrypted = false; let keyUrl = null; let iv = null; // 获取m3u8文件 this.fetchWithHeaders(resource.url, resource.headers || {}) .then(response => response.text()) .then(playlist => { // 解析m3u8播放列表 const lines = playlist.split('\n').filter(line => line.trim() !== ''); // 检查是否加密 for (const line of lines) { if (line.startsWith('#EXT-X-KEY')) { isEncrypted = true; const keyMatch = line.match(/URI="([^"]+)"/); if (keyMatch) { keyUrl = new URL(keyMatch[1], resource.url).href; } const ivMatch = line.match(/IV=([0-9A-Fa-f]+)/); if (ivMatch) { iv = ivMatch[1]; } break; } } // 提取TS片段 const tsUrls = []; for (let i = 0; i < lines.length; i++) { if (!lines[i].startsWith('#')) { // 处理相对路径 tsUrls.push(new URL(lines[i], resource.url).href); } } totalSize = tsUrls.length; console.log(`找到 ${totalSize} 个TS片段`); // 下载TS片段 const downloadPromises = tsUrls.map((tsUrl, index) => { return this.fetchWithHeaders(tsUrl, resource.headers || {}) .then(response => response.arrayBuffer()) .then(buffer => { downloadedSize++; const progress = ((downloadedSize / totalSize) * 100).toFixed(2); console.log(`视频片段下载进度: ${progress}%`); this.updateDownloadProgress(resource.id, progress); // 如果加密,进行解密 if (isEncrypted && keyUrl) { // 这里应该实现解密逻辑 console.warn('视频已加密,解密功能待实现'); return buffer; } return buffer; }); }); // 等待所有片段下载完成 Promise.all(downloadPromises) .then(buffers => { console.log(`所有TS片段下载完成,开始合并`); // 合并所有片段 const mergedBuffer = new Uint8Array(buffers.reduce((acc, buffer) => acc + buffer.byteLength, 0)); let offset = 0; buffers.forEach(buffer => { mergedBuffer.set(new Uint8Array(buffer), offset); offset += buffer.byteLength; }); // 创建下载链接 const blob = new Blob([mergedBuffer], { type: 'video/mp4' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = resource.name.replace(/\.m3u8$/, '.mp4'); document.body.appendChild(a); a.click(); // 清理 setTimeout(() => { document.body.removeChild(a); URL.revokeObjectURL(url); this.removeDownloadProgress(resource.id); }, 1000); }) .catch(error => { console.error('下载TS片段失败:', error); this.updateDownloadProgress(resource.id, '错误', true); }); }) .catch(error => { console.error('获取m3u8文件失败:', error); this.updateDownloadProgress(resource.id, '错误', true); }); } // 创建带请求头的fetch fetchWithHeaders(url, headers) { return fetch(url, { headers: headers, credentials: 'include' }); } // 创建下载进度条 createDownloadProgress(id, name) { // 检查是否已存在进度条 if (document.getElementById(`download-progress-${id}`)) { return; } const progressContainer = document.createElement('div'); progressContainer.id = `download-progress-${id}`; progressContainer.className = 'download-progress'; progressContainer.innerHTML = ` <div class="progress-title">${this.truncateText(name, 30)}</div> <div class="progress-bar"> <div class="progress-value">0%</div> </div> `; // 添加到面板 if (this.panelElement) { this.panelElement.appendChild(progressContainer); } else { document.body.appendChild(progressContainer); } } // 更新下载进度 updateDownloadProgress(id, progress, isError = false) { const progressContainer = document.getElementById(`download-progress-${id}`); if (!progressContainer) return; const progressBar = progressContainer.querySelector('.progress-bar'); const progressValue = progressContainer.querySelector('.progress-value'); if (progressBar && progressValue) { if (isError) { progressBar.style.backgroundColor = '#ff4d4f'; progressValue.textContent = '下载失败'; } else { progressBar.style.width = `${progress}%`; progressValue.textContent = `${progress}%`; } } } // 移除下载进度条 removeDownloadProgress(id) { const progressContainer = document.getElementById(`download-progress-${id}`); if (progressContainer) { progressContainer.remove(); } } // 拦截请求 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')) { // 抖音API处理 if (url.includes('v3-web-prime.douyinvod.com/video/') || url.includes('v26-web-prime.douyinvod.com/video/') || url.includes('douyin.com/aweme/v1/play/?file_id=')) { return url; } return url; } // YouTube if (url.includes('youtube.com') || url.includes('youtu.be')) { // 提取真实视频URL if (url.includes('youtube.com/watch')) { const videoId = new URL(url).searchParams.get('v'); if (videoId) { return `https://www.youtube.com/get_video_info?video_id=${videoId}`; } } return url; } // B站 if (url.includes('bilibili.com')) { // B站API处理 if (url.includes('api.bilibili.com/x/player/playurl')) { return url; } // 视频页面URL if (url.includes('bilibili.com/video/') || url.includes('bilibili.com/bangumi/')) { // 提取avid和cid const match = url.match(/(av\d+|BV\w+)/); if (match) { const aid = match[0]; return `https://api.bilibili.com/x/player/playurl?avid=${aid}&cid=0&qn=120`; } } return url; } // 央视网 if (url.includes('cntv')) { if (url.includes('/asp/')) { // 央视网URL特殊处理 const realUrl = url.replace(/.+?cntv.*?\/asp\/.*?hls\/(.*)/, 'https://hls.cntv.myalicdn.com/asp/hls/$1'); return realUrl; } return url; } // 通用视频格式检测 const videoExtensions = ['.m3u8', '.mp4', '.webm', '.flv', '.avi', '.mov', '.f4v', '.mkv', '.rmvb', '.wmv', '.3gp', '.ts']; for (const ext of videoExtensions) { if (url.includes(ext)) { return url; } } // 处理没有扩展名但可能是视频的URL const videoKeywords = ['video', 'stream', 'media', 'play', 'source', 'file', 'vod']; for (const keyword of videoKeywords) { if (url.toLowerCase().includes(keyword)) { // 检查是否为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; let filename = pathname.split('/').pop() || 'unknown'; let extension = filename.split('.').pop().toLowerCase(); const siteInfo = this.getSiteInfo(url); // 确定资源类型 let type = 'other'; let quality = 'unknown'; let duration = 0; // 视频扩展名和关键词 const videoExtensions = ['.m3u8', '.mp4', '.webm', '.flv', '.avi', '.mov', '.f4v', '.mkv', '.rmvb', '.wmv', '.3gp', '.ts']; const videoKeywords = ['video', 'stream', 'media', 'play', 'source', 'vod', 'watch']; // 音频扩展名 const audioExtensions = ['.mp3', '.wav', '.flac', '.aac', '.ogg', '.m4a', '.opus']; // 图片扩展名 const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg']; // 文档扩展名 const documentExtensions = ['.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx']; // 压缩包扩展名 const archiveExtensions = ['.zip', '.rar', '.7z', '.tar', '.gz']; // 特殊处理没有扩展名但可能是视频的URL if (extension === filename) { // 检查是否为视频URL if (url.includes('.m3u8') || videoKeywords.some(keyword => url.toLowerCase().includes(keyword))) { type = 'video'; extension = 'mp4'; // 假设默认视频格式 } } else { // 根据扩展名确定资源类型 if (videoExtensions.includes('.' + extension)) { type = 'video'; } else if (audioExtensions.includes('.' + extension)) { type = 'audio'; } else if (imageExtensions.includes('.' + extension)) { type = 'image'; } else if (documentExtensions.includes('.' + extension)) { type = 'document'; } else if (archiveExtensions.includes('.' + extension)) { type = 'archive'; } else { // 检查是否为视频URL关键词 if (videoKeywords.some(keyword => url.toLowerCase().includes(keyword))) { type = 'video'; extension = 'mp4'; } } } // 额外检查:如果URL包含视频相关关键词但类型不是视频 if (type !== 'video' && (url.includes('.m3u8') || videoKeywords.some(keyword => url.toLowerCase().includes(keyword)))) { type = 'video'; extension = 'mp4'; } // 尝试提取视频质量 if (type === 'video') { if (url.includes('quality')) { const qualityMatch = url.match(/quality=(\d+)/); if (qualityMatch) { quality = `${qualityMatch[1]}p`; } } else if (url.includes('resolution')) { const resolutionMatch = url.match(/resolution=(\d+x\d+)/); if (resolutionMatch) { quality = resolutionMatch[1]; } } else if (url.includes('1080') || url.includes('fhd')) { quality = '1080p'; } else if (url.includes('720') || url.includes('hd')) { quality = '720p'; } else if (url.includes('480') || url.includes('sd')) { quality = '480p'; } else if (url.includes('360')) { quality = '360p'; } } // 网站特定处理 let headers = {}; if (siteInfo.name === 'ixigua') { headers.Referer = 'https://www.ixigua.com/'; headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36'; } else if (siteInfo.name === 'douyin') { headers.Referer = 'https://www.douyin.com/'; headers['User-Agent'] = 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1'; } else if (siteInfo.name === 'cntv') { headers.Referer = 'https://tv.cctv.com/'; headers['Origin'] = 'https://tv.cctv.com'; // 央视网特定处理 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.name === 'javplayer') { headers.Referer = 'https://javplayer.me/'; } else if (siteInfo.name === 'aliyundrive') { headers.Referer = 'https://www.aliyundrive.com/'; } else if (siteInfo.name === 'bilibili') { headers.Referer = 'https://www.bilibili.com/'; headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36'; } else if (siteInfo.name === 'youtube') { headers.Referer = 'https://www.youtube.com/'; } // 添加通用请求头以提高兼容性 if (!headers['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; } } // 优化文件名 if (filename === 'unknown' || filename.length > 100) { filename = `${type}_${Date.now()}.${extension}`; } else if (!filename.includes('.')) { filename = `${filename}.${extension}`; } return { id: url + Date.now().toString(), // 确保ID唯一 url: url, name: filename, extension: extension, type: type, size: size, duration: duration, quality: quality, timestamp: Date.now(), // 添加网站特定信息 site: siteInfo.name, siteIcon: siteInfo.icon, siteCategory: siteInfo.category, // 添加请求头信息 headers: headers }; } catch (error) { console.error('解析URL失败:', error); return null; } } // 获取网站信息 getSiteInfo(url) { let name = 'other'; let icon = '🔍'; let category = 'general'; if (url.includes('ixigua.com')) { name = 'ixigua'; icon = '🍉'; category = 'video'; } else if (url.includes('douyin.com') || url.includes('douyinvod.com')) { name = 'douyin'; icon = '🎵'; category = 'video'; } else if (url.includes('youtube.com') || url.includes('youtu.be')) { name = 'youtube'; icon = '▶️'; category = 'video'; } else if (url.includes('bilibili.com') || url.includes('bilibili.tv')) { name = 'bilibili'; icon = '📱'; category = 'video'; } else if (url.includes('cntv') || url.includes('cctv.com')) { name = 'cntv'; icon = '📺'; category = 'video'; } else if (url.includes('javplayer.me')) { name = 'javplayer'; icon = '🎬'; category = 'video'; } else if (url.includes('aliyundrive.com')) { name = 'aliyundrive'; icon = '☁️'; category = 'storage'; } else if (url.includes('weibo.cn') || url.includes('weibo.com')) { name = 'weibo'; icon = '🐦'; category = 'social'; } else if (url.includes('qq.com') || url.includes('qzone.qq.com') || url.includes('v.qq.com')) { name = 'qq'; icon = '🐧'; category = 'video'; } else if (url.includes('music.163.com') || url.includes('netease.com')) { name = 'netease'; icon = '🎶'; category = 'music'; } else if (url.includes('qqmusic.qq.com')) { name = 'qqmusic'; icon = '🎵'; category = 'music'; } else if (url.includes('spotify.com')) { name = 'spotify'; icon = '🎧'; category = 'music'; } else if (url.includes('iwara.tv')) { name = 'iwara'; icon = '🎮'; category = 'video'; } else if (url.includes('telegram.org') || url.includes('t.me')) { name = 'telegram'; icon = '✉️'; category = 'social'; } else if (url.includes('github.com')) { name = 'github'; icon = '💻'; category = 'code'; } return { name: name, icon: icon, category: category }; } // 添加资源 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); } // 监听视频属性变化 const observer = new MutationObserver(mutations => { mutations.forEach(mutation => { if (mutation.attributeName === 'src' && video.src) { this.handleRequest(video.src); } }); }); observer.observe(video, { attributes: true }); }); // 检查音频 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); } }); // 检查iframe中的媒体元素 document.querySelectorAll('iframe:not([data-sniffed])').forEach(iframe => { try { iframe.dataset.sniffed = 'true'; const iframeDoc = iframe.contentDocument || iframe.contentWindow.document; if (iframeDoc) { // 检查iframe中的图片 iframeDoc.querySelectorAll('img:not([data-sniffed])').forEach(img => { img.dataset.sniffed = 'true'; const url = img.src; this.handleRequest(url); }); // 检查iframe中的视频 iframeDoc.querySelectorAll('video:not([data-sniffed])').forEach(video => { video.dataset.sniffed = 'true'; const url = video.src; this.handleRequest(url); }); } } catch (e) { // 跨域iframe无法访问,忽略 } }); }, 2000); } // 获取当前播放的视频URL getCurrentVideoUrl() { try { // 检查页面中是否有正在播放的视频 const videos = document.querySelectorAll('video'); for (const video of videos) { if (!video.paused) { return video.currentSrc || video.src; } } // 如果没有正在播放的视频,检查是否有最近加载的视频 if (videos.length > 0) { return videos[videos.length - 1].currentSrc || videos[videos.length - 1].src; } return null; } catch (error) { console.error('获取当前播放视频URL失败:', error); return null; } } // 格式化大小 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); });
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址