// ==UserScript== // @name M3U8 资源终极嗅探器 // @namespace https://github.com/Orochi-Adde/m3u8-downloader // @version 1.01 // @description m3u8-downloader 专属解析适配,智能突破防爬限制,原生零损耗拉伸 // @author Orochi-Adde // @match *://*/* // @grant GM_setClipboard // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @connect * // @run-at document-start // @license MIT // @homepageURL https://github.com/Orochi-Adde/m3u8-downloader // @supportURL https://github.com/Orochi-Adde/m3u8-downloader/issues // @run-at document-start // @downloadURL https://update.greasyfork.icu/scripts/575410/M3U8%20%E8%B5%84%E6%BA%90%E7%BB%88%E6%9E%81%E5%97%85%E6%8E%A2%E5%99%A8.user.js // @updateURL https://update.greasyfork.icu/scripts/575410/M3U8%20%E8%B5%84%E6%BA%90%E7%BB%88%E6%9E%81%E5%97%85%E6%8E%A2%E5%99%A8.meta.js // ==/UserScript== (function() { 'use strict'; const isTopWindow = window === window.top; const m3u8List = new Set(); let uiContainer = null; let minimizedIcon = null; let tbodyElement = null; let domObserver = null; let isForceParseMode = false; let isUiMinimized = false; const COLORS = { main: '#27ae60', ad: '#e74c3c', host: '#aaaaaa', fileNormal: '#85c1e9', fileMaster: '#f39c12', child: '#1abc9c', btnParse: '#8e44ad', warning: '#f1c40f', safe: '#2ecc71' }; // ========================================== // 🌟 核心新增:底层网络 API 劫持 (Monkey Patching) // 解决延时加载、点击播放、广告后加载的核心武器 // ========================================== function hijackNetwork() { // 1. 劫持 Fetch API const originalFetch = window.fetch; window.fetch = async function(...args) { try { const url = args[0] instanceof Request ? args[0].url : args[0]; if (typeof url === 'string' && url.includes('.m3u8')) { // 只要代码发起了 m3u8 请求,瞬间捕获! processSniffedUrl(url, false); } } catch (e) { console.error("Fetch Intercept Error", e); } return originalFetch.apply(this, args); }; // 2. 劫持 XMLHttpRequest (XHR) const originalXhrOpen = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function(method, url, ...rest) { try { if (typeof url === 'string' && url.includes('.m3u8')) { // 兼容老式 Ajax 请求 processSniffedUrl(url, false); } } catch (e) { console.error("XHR Intercept Error", e); } return originalXhrOpen.call(this, method, url, ...rest); }; } // 必须在 document-start 阶段立刻执行,抢在网页自身 JS 运行之前! hijackNetwork(); // --- 读取本地持久化配置 --- let savedProxyEnable = false; let savedProxyUrl = 'socks5://127.0.0.1:10808'; try { if (typeof GM_getValue !== 'undefined') { savedProxyEnable = GM_getValue('gemini_proxy_enable', false); savedProxyUrl = GM_getValue('gemini_proxy_url', 'socks5://127.0.0.1:10808'); } } catch (e) {} function safeSaveConfig(key, value) { try { if (typeof GM_setValue !== 'undefined') GM_setValue(key, value); } catch (e) {} } function getAutoFilename() { try { let host = window.location.hostname.replace(/^www\./i, ''); let paths = window.location.pathname.split('/').filter(p => p.trim() !== ''); let lastDir = paths.length > 0 ? paths[paths.length - 1] : 'video'; lastDir = decodeURIComponent(lastDir).replace(/[\\/:*?"<>|]/g, '_'); return `[${host}] ${lastDir}`; } catch (e) { return `video_${Math.floor(Date.now()/1000)}`; } } function makeRequest(url, strategy) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: "GET", url: url, headers: strategy.headers, anonymous: strategy.anonymous, onload: resolve, onerror: reject }); }); } function toggleUI(action) { if (!uiContainer || !minimizedIcon) return; if (action === 'show') { isUiMinimized = false; uiContainer.style.setProperty('display', 'flex', 'important'); minimizedIcon.style.setProperty('display', 'none', 'important'); } else if (action === 'minimize') { isUiMinimized = true; uiContainer.style.setProperty('display', 'none', 'important'); minimizedIcon.style.setProperty('display', 'flex', 'important'); } else if (action === 'close') { isUiMinimized = true; uiContainer.style.setProperty('display', 'none', 'important'); minimizedIcon.style.setProperty('display', 'none', 'important'); } else { if (uiContainer.style.display !== 'none') toggleUI('minimize'); else toggleUI('show'); } } async function smartParseM3u8(url, parentTr, parseBtn, tdFile) { parseBtn.disabled = true; const baseHeaders = { 'Cache-Control': 'no-cache, no-store, must-revalidate' }; const strategies = [ { id: 'bare', name: '裸解析', anonymous: true, headers: { ...baseHeaders } }, { id: 'ref', name: '加 Referer', anonymous: true, headers: { ...baseHeaders, 'Referer': window.location.href, 'Origin': window.location.origin } }, { id: 'cookie', name: '加 Cookie', anonymous: false, headers: { ...baseHeaders, 'Referer': window.location.href, 'Origin': window.location.origin } }, { id: 'headers', name: '全量 Headers', anonymous: false, headers: { ...baseHeaders, 'Referer': window.location.href, 'Origin': window.location.origin, 'User-Agent': navigator.userAgent, 'Accept': '*/*' } } ]; let successRes = null, usedStrategy = null; for (let strategy of strategies) { parseBtn.innerText = `试[${strategy.id}]..`; parseBtn.style.background = '#34495e'; try { const res = await makeRequest(url, strategy); if (res.status >= 200 && res.status < 300) { successRes = res; usedStrategy = strategy; break; } } catch (e) {} } if (successRes) { const lines = successRes.responseText.split('\n'); const results = []; let isMaster = false, lastResolution = '默认画质'; for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); if (line.startsWith('#EXT-X-STREAM-INF')) { isMaster = true; const resMatch = line.match(/RESOLUTION=(\d+x\d+)/); lastResolution = resMatch ? resMatch[1] : '未知画质'; } else if (line.endsWith('.m3u8') && !line.startsWith('#')) { isMaster = true; try { results.push({ url: new URL(line, url).href, res: lastResolution }); lastResolution = '默认画质'; } catch(e) {} } } let hintDiv = tdFile.querySelector('.gemini-hint'); if (!hintDiv) { hintDiv = document.createElement('div'); hintDiv.className = 'gemini-hint'; hintDiv.style.cssText = `font-size: 10px; margin-top: 4px; border-top: 1px dashed #555; padding-top: 2px; line-height: 1.2; white-space: normal;`; tdFile.appendChild(hintDiv); } if (usedStrategy.id !== 'bare') { if (['ref', 'cookie', 'headers'].includes(usedStrategy.id)) document.getElementById('chk-ref').checked = true; if (['cookie', 'headers'].includes(usedStrategy.id)) document.getElementById('chk-cookie').checked = true; if (usedStrategy.id === 'headers') document.getElementById('chk-ua').checked = true; hintDiv.style.color = COLORS.warning; hintDiv.innerHTML = `🛡️ 防爬拦截: 已验证必须挂载 [${usedStrategy.name}] 参数`; } else { document.getElementById('chk-ref').checked = false; document.getElementById('chk-cookie').checked = false; document.getElementById('chk-ua').checked = false; hintDiv.style.color = COLORS.safe; hintDiv.innerHTML = `✅ 纯净资源: 无任何防盗链,可直接裸连下载`; } if (isMaster && results.length > 0) { parseBtn.innerText = '✔ 展开嵌套'; parseBtn.style.background = COLORS.main; for (let i = results.length - 1; i >= 0; i--) { if (!m3u8List.has(results[i].url)) { m3u8List.add(results[i].url); addRowToTable(results[i].url, false, { isChild: true, res: results[i].res, insertAfter: parentTr, inheritedStrategy: usedStrategy }); } } } else { parseBtn.innerText = '底层文件'; parseBtn.style.background = '#7f8c8d'; } } else { parseBtn.innerText = '❌ 防爬极严'; parseBtn.style.background = COLORS.ad; } setTimeout(() => { parseBtn.innerText = '🔍 探测解析'; parseBtn.style.background = COLORS.btnParse; parseBtn.disabled = false; }, 3000); } function getTitleHTML(count) { return ` 🔍 M3U8 嗅探列表 (${count}) m3u8-downloader专用解析 (Alt+M 显隐) `; } function initUI() { if (!isTopWindow || uiContainer || !document.documentElement) return; minimizedIcon = document.createElement('div'); minimizedIcon.style.cssText = ` display: none !important; position: fixed !important; top: 15% !important; right: 20px !important; z-index: 2147483647 !important; width: 44px; height: 44px; border-radius: 50%; background: rgba(39, 174, 96, 0.85); backdrop-filter: blur(5px); color: white; justify-content: center; align-items: center; cursor: pointer; font-size: 20px; box-shadow: 0 4px 15px rgba(0,0,0,0.5); border: 2px solid #2ecc71; transition: transform 0.2s; user-select: none; `; minimizedIcon.innerHTML = '🔍'; minimizedIcon.title = 'M3U8 嗅探器 (点击展开)'; minimizedIcon.onmouseover = () => { minimizedIcon.style.transform = 'scale(1.1)'; }; minimizedIcon.onmouseout = () => { minimizedIcon.style.transform = 'scale(1)'; }; minimizedIcon.onclick = () => toggleUI('show'); document.documentElement.appendChild(minimizedIcon); uiContainer = document.createElement('div'); uiContainer.style.cssText = ` position: fixed !important; top: 10% !important; right: 20px !important; z-index: 2147483647 !important; background: rgba(18, 18, 18, 0.95) !important; color: #d4d4d4 !important; padding: 15px !important; border-radius: 8px !important; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.6) !important; border: 1px solid #333 !important; width: clamp(380px, 45vw, 620px); height: 450px; min-width: 380px !important; min-height: 200px !important; max-width: 95vw !important; max-height: 95vh !important; resize: both !important; overflow: hidden !important; font-family: Consolas, monospace !important; font-size: 12px !important; flex-direction: column !important; gap: 10px !important; backdrop-filter: blur(10px); display: none !important; `; const header = document.createElement('div'); header.style.cssText = 'display: flex; justify-content: space-between; align-items: center; cursor: move; padding-bottom: 8px; border-bottom: 1px solid rgba(255,255,255,0.1); flex-shrink: 0;'; const title = document.createElement('span'); title.id = 'm3u8-sniffer-title'; title.innerHTML = getTitleHTML(0); title.style.cssText = 'font-weight: bold; font-size: 14px; color: #fff; display: flex; align-items: center;'; const btnGroup = document.createElement('div'); btnGroup.style.display = 'flex'; btnGroup.style.alignItems = 'center'; const advancedBtn = document.createElement('span'); advancedBtn.innerText = '⚙️ 参数构造器'; advancedBtn.style.cssText = 'cursor: pointer; color: #f1c40f; font-size: 12px; margin-right: 15px; font-weight: bold; border-bottom: 1px dashed #f1c40f; user-select: none;'; const minBtn = document.createElement('span'); minBtn.innerText = '➖'; minBtn.title = '最小化为图标'; minBtn.style.cssText = 'cursor: pointer; color: #f1c40f; font-size: 14px; font-weight: bold; padding: 0 8px; transition: 0.2s; user-select: none;'; minBtn.onmouseover = () => { minBtn.style.color = '#fff'; }; minBtn.onmouseout = () => { minBtn.style.color = '#f1c40f'; }; minBtn.onclick = () => toggleUI('minimize'); const closeBtn = document.createElement('span'); closeBtn.innerText = '✖'; closeBtn.title = '彻底隐藏 (Alt+M 可唤醒)'; closeBtn.style.cssText = 'cursor: pointer; color: #e74c3c; font-size: 14px; font-weight: bold; padding: 0 5px; transition: 0.2s; user-select: none;'; closeBtn.onmouseover = () => { closeBtn.style.color = '#ff7675'; }; closeBtn.onmouseout = () => { closeBtn.style.color = '#e74c3c'; }; closeBtn.onclick = () => toggleUI('close'); btnGroup.appendChild(advancedBtn); btnGroup.appendChild(minBtn); btnGroup.appendChild(closeBtn); header.appendChild(title); header.appendChild(btnGroup); const advancedPanel = document.createElement('div'); advancedPanel.style.cssText = 'display: none; background: rgba(0,0,0,0.5); padding: 10px; border-radius: 6px; border: 1px dashed #7f8c8d; flex-direction: column; gap: 8px; margin-top: -5px; flex-shrink: 0;'; advancedPanel.innerHTML = `