// ==UserScript== // @name MPV-M3U8 Video Detector and Downloader // @name:en MPV-M3U8 Video Detector and Downloader // @version 1.5.6 // @description:en Automatically detect the m3u8 video of the page and download it completely. Once detected the m3u8 link, it will appear in the upper right corner of the page. Click download to jump to the m3u8 downloader. // @icon https://tools.thatwind.com/favicon.png // @author - // @namespace https://tools.thatwind.com/ // @homepage // @match *://*/* // @exclude *://www.diancigaoshou.com/* // @connect * // @grant unsafeWindow // @grant GM_openInTab // @grant GM.openInTab // @grant GM_getValue // @grant GM.getValue // @grant GM_setValue // @grant GM.setValue // @grant GM_deleteValue // @grant GM.deleteValue // @grant GM_xmlhttpRequest // @grant GM.xmlHttpRequest // @grant GM_download // @run-at document-start // ==/UserScript== // inline m3u8 parser to improve performance and longevity /*! @name m3u8-parser @version 4.7.1 @license Apache-2.0 */ !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("global/window")):"function"==typeof define&&define.amd?define(["exports","global/window"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).m3u8Parser={},t.window)}(this,(function(t,e){"use strict";function i(t){return t&&"object"==typeof t&&"default"in t?t:{default:t}}var r=i(e);var a=function(t,e){t.prototype=Object.create(e.prototype),t.prototype.constructor=t,t.__proto__=e},s=function(){function t(){this.listeners={}}var e=t.prototype;return e.on=function(t,e){this.listeners[t]||(this.listeners[t]=[]),this.listeners[t].push(e)},e.off=function(t,e){if(!this.listeners[t])return!1;var i=this.listeners[t].indexOf(e);return this.listeners[t]=this.listeners[t].slice(0),this.listeners[t].splice(i,1),i>-1},e.trigger=function(t){var e=this.listeners[t];if(e)if(2===arguments.length)for(var i=e.length,r=0;r-1;e=this.buffer.indexOf("\n"))this.trigger("data",this.buffer.substring(0,e)),this.buffer=this.buffer.substring(e+1)},e}(s);var u=function(t,e,i){return t(i={path:e,exports:{},require:function(t,e){return function(){throw new Error("Dynamic requires are not currently supported by @rollup/plugin-commonjs")}(null==e&&i.path)}},i.exports),i.exports}((function(t){function e(){return t.exports=e=Object.assign||function(t){for(var e=1;e0&&(o.duration=t.duration),0===t.duration&&(o.duration=.01,this.trigger("info",{message:"updating zero segment duration to a small value"})),this.manifest.segments=s},key:function(){if(t.attributes)if("NONE"!==t.attributes.METHOD)if(t.attributes.URI){if("com.apple.streamingkeydelivery"===t.attributes.KEYFORMAT)return this.manifest.contentProtection=this.manifest.contentProtection||{},void(this.manifest.contentProtection["com.apple.fps.1_0"]={attributes:t.attributes});if("com.microsoft.playready"===t.attributes.KEYFORMAT)return this.manifest.contentProtection=this.manifest.contentProtection||{},void(this.manifest.contentProtection["com.microsoft.playready"]={uri:t.attributes.URI});if("urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"===t.attributes.KEYFORMAT){return-1===["SAMPLE-AES","SAMPLE-AES-CTR","SAMPLE-AES-CENC"].indexOf(t.attributes.METHOD)?void this.trigger("warn",{message:"invalid key method provided for Widevine"}):("SAMPLE-AES-CENC"===t.attributes.METHOD&&this.trigger("warn",{message:"SAMPLE-AES-CENC is deprecated, please use SAMPLE-AES-CTR instead"}),"data:text/plain;base64,"!==t.attributes.URI.substring(0,23)?void this.trigger("warn",{message:"invalid key URI provided for Widevine"}):t.attributes.KEYID&&"0x"===t.attributes.KEYID.substring(0,2)?(this.manifest.contentProtection=this.manifest.contentProtection||{},void(this.manifest.contentProtection["com.widevine.alpha"]={attributes:{schemeIdUri:t.attributes.KEYFORMAT,keyId:t.attributes.KEYID.substring(2)},pssh:d(t.attributes.URI.split(",")[1])})):void this.trigger("warn",{message:"invalid key ID provided for Widevine"}))}t.attributes.METHOD||this.trigger("warn",{message:"defaulting key method to AES-128"}),r={method:t.attributes.METHOD||"AES-128",uri:t.attributes.URI},void 0!==t.attributes.IV&&(r.iv=t.attributes.IV)}else this.trigger("warn",{message:"ignoring key declaration without URI"});else r=null;else this.trigger("warn",{message:"ignoring key declaration without attribute list"})},"media-sequence":function(){isFinite(t.number)?this.manifest.mediaSequence=t.number:this.trigger("warn",{message:"ignoring invalid media sequence: "+t.number})},"discontinuity-sequence":function(){isFinite(t.number)?(this.manifest.discontinuitySequence=t.number,E=t.number):this.trigger("warn",{message:"ignoring invalid discontinuity sequence: "+t.number})},"playlist-type":function(){/VOD|EVENT/.test(t.playlistType)?this.manifest.playlistType=t.playlistType:this.trigger("warn",{message:"ignoring unknown playlist type: "+t.playlist})},map:function(){i={},t.uri&&(i.uri=t.uri),t.byterange&&(i.byterange=t.byterange),r&&(i.key=r)},"stream-inf":function(){this.manifest.playlists=s,this.manifest.mediaGroups=this.manifest.mediaGroups||b,t.attributes?(o.attributes||(o.attributes={}),u(o.attributes,t.attributes)):this.trigger("warn",{message:"ignoring empty stream-inf attributes"})},media:function(){if(this.manifest.mediaGroups=this.manifest.mediaGroups||b,t.attributes&&t.attributes.TYPE&&t.attributes["GROUP-ID"]&&t.attributes.NAME){var i=this.manifest.mediaGroups[t.attributes.TYPE];i[t.attributes["GROUP-ID"]]=i[t.attributes["GROUP-ID"]]||{},e=i[t.attributes["GROUP-ID"]],(n={default:/yes/i.test(t.attributes.DEFAULT)}).default?n.autoselect=!0:n.autoselect=/yes/i.test(t.attributes.AUTOSELECT),t.attributes.LANGUAGE&&(n.language=t.attributes.LANGUAGE),t.attributes.URI&&(n.uri=t.attributes.URI),t.attributes["INSTREAM-ID"]&&(n.instreamId=t.attributes["INSTREAM-ID"]),t.attributes.CHARACTERISTICS&&(n.characteristics=t.attributes.CHARACTERISTICS),t.attributes.FORCED&&(n.forced=/yes/i.test(t.attributes.FORCED)),e[t.attributes.NAME]=n}else this.trigger("warn",{message:"ignoring incomplete or missing media group"})},discontinuity:function(){E+=1,o.discontinuity=!0,this.manifest.discontinuityStarts.push(s.length)},"program-date-time":function(){void 0===this.manifest.dateTimeString&&(this.manifest.dateTimeString=t.dateTimeString,this.manifest.dateTimeObject=t.dateTimeObject),o.dateTimeString=t.dateTimeString,o.dateTimeObject=t.dateTimeObject},targetduration:function(){!isFinite(t.duration)||t.duration<0?this.trigger("warn",{message:"ignoring invalid target duration: "+t.duration}):(this.manifest.targetDuration=t.duration,l.call(this,this.manifest))},start:function(){t.attributes&&!isNaN(t.attributes["TIME-OFFSET"])?this.manifest.start={timeOffset:t.attributes["TIME-OFFSET"],precise:t.attributes.PRECISE}:this.trigger("warn",{message:"ignoring start declaration without appropriate attribute list"})},"cue-out":function(){o.cueOut=t.data},"cue-out-cont":function(){o.cueOutCont=t.data},"cue-in":function(){o.cueIn=t.data},skip:function(){this.manifest.skip=h(t.attributes),this.warnOnMissingAttributes_("#EXT-X-SKIP",t.attributes,["SKIPPED-SEGMENTS"])},part:function(){var e=this;g=!0;var i=this.manifest.segments.length,r=h(t.attributes);o.parts=o.parts||[],o.parts.push(r),r.byterange&&(r.byterange.hasOwnProperty("offset")||(r.byterange.offset=m),m=r.byterange.offset+r.byterange.length);var a=o.parts.length-1;this.warnOnMissingAttributes_("#EXT-X-PART #"+a+" for segment #"+i,t.attributes,["URI","DURATION"]),this.manifest.renditionReports&&this.manifest.renditionReports.forEach((function(t,i){t.hasOwnProperty("lastPart")||e.trigger("warn",{message:"#EXT-X-RENDITION-REPORT #"+i+" lacks required attribute(s): LAST-PART"})}))},"server-control":function(){var e=this.manifest.serverControl=h(t.attributes);e.hasOwnProperty("canBlockReload")||(e.canBlockReload=!1,this.trigger("info",{message:"#EXT-X-SERVER-CONTROL defaulting CAN-BLOCK-RELOAD to false"})),l.call(this,this.manifest),e.canSkipDateranges&&!e.hasOwnProperty("canSkipUntil")&&this.trigger("warn",{message:"#EXT-X-SERVER-CONTROL lacks required attribute CAN-SKIP-UNTIL which is required when CAN-SKIP-DATERANGES is set"})},"preload-hint":function(){var e=this.manifest.segments.length,i=h(t.attributes),r=i.type&&"PART"===i.type;o.preloadHints=o.preloadHints||[],o.preloadHints.push(i),i.byterange&&(i.byterange.hasOwnProperty("offset")||(i.byterange.offset=r?m:0,r&&(m=i.byterange.offset+i.byterange.length)));var a=o.preloadHints.length-1;if(this.warnOnMissingAttributes_("#EXT-X-PRELOAD-HINT #"+a+" for segment #"+e,t.attributes,["TYPE","URI"]),i.type)for(var s=0;s { p.removeChild(mdiv); }, disappearTime); } }; if (location.host === "tools.thatwind.com" || location.host === "localhost:3000") { mgmapi.addStyle("#userscript-tip{display:none !important;}"); // 对请求做代理 const _fetch = unsafeWindow.fetch; unsafeWindow.fetch = async function (...args) { try { let response = await _fetch(...args); if (response.status !== 200) throw new Error(response.status); return response; } catch (e) { // 失败请求使用代理 if (args.length == 1) { console.log(`请求代理:${args[0]}`); return await new Promise((resolve, reject) => { let referer = new URLSearchParams(location.hash.slice(1)).get("referer"); let headers = {}; if (referer) { referer = new URL(referer); headers = { "origin": referer.origin, "referer": referer.href }; } mgmapi.xmlHttpRequest({ method: "GET", url: args[0], responseType: 'arraybuffer', headers, onload(r) { resolve({ status: r.status, headers: new Headers(r.responseHeaders.split("\n").filter(n => n).map(s => s.split(/:\s*/)).reduce((all, [a, b]) => { all[a] = b; return all; }, {})), async text() { return r.responseText; }, async arrayBuffer() { return r.response; } }); }, onerror() { reject(new Error()); } }); }); } else { throw e; } } } return; } // iframe 信息交流 // 目前只用于获取顶部标题 window.addEventListener("message", async (e) => { if (e.data === "3j4t9uj349-gm-get-title") { let name = `top-title-${Date.now()}`; await mgmapi.setValue(name, document.title); e.source.postMessage(`3j4t9uj349-gm-top-title-name:${name}`, "*"); } }); function getTopTitle() { return new Promise(resolve => { window.addEventListener("message", async function l(e) { if (typeof e.data === "string") { if (e.data.startsWith("3j4t9uj349-gm-top-title-name:")) { let name = e.data.slice("3j4t9uj349-gm-top-title-name:".length); await new Promise(r => setTimeout(r, 5)); // 等5毫秒 确定 setValue 已经写入 resolve(await mgmapi.getValue(name)); mgmapi.deleteValue(name); window.removeEventListener("message", l); } } }); window.top.postMessage("3j4t9uj349-gm-get-title", "*"); }); } { // 请求检测 // const _fetch = self.fetch; // self.fetch = function (...args) { // if (checkUrl(args[0])) doM3U({ url: args[0] }); // return _fetch(...args); // } // overriding fetch could be dangerous and slow, only apply to socolive if (location.href.match(/^.*?socolive.*?$/)) { var sfetch = unsafeWindow.fetch; unsafeWindow.fetch = new Proxy(sfetch, { apply: function(target, thisArg, args) { console.log(target, thisArg, args); let proceed = true; try { if (args[0].indexOf(".flv") != -1) doM3U({ url: args[0], content: args[0] }); } catch(ex) { console.log(ex); } return proceed ? Reflect.apply(target, thisArg, args) : Promise.resolve(new Response()); } }); } const _r_text = unsafeWindow.Response.prototype.text; unsafeWindow.Response.prototype.text = function () { return new Promise((resolve, reject) => { _r_text.call(this).then((text) => { resolve(text); if (checkContent(text)) doM3U({ url: this.url, content: text }); if (checkUrl(this.url)) doM3U({ url: this.url }); }).catch(reject); }); } const _open = unsafeWindow.XMLHttpRequest.prototype.open; unsafeWindow.XMLHttpRequest.prototype.open = function (...args) { this.addEventListener("load", () => { try { let content = this.responseText; if (checkContent(content)) doM3U({ url: args[1], content: content }); } catch { } }); if (checkUrl(args[1])) doM3U({ url: args[1] }); return _open.apply(this, args); } function checkUrl(url) { url = new URL(url, location.href); if (url.pathname.indexOf(".m3u8") != -1 || url.pathname.indexOf(".m3u") != -1) { // 发现 return true; } } function checkContent(content) { if (content.trim().startsWith("#EXTM3U")) { return true; } } // 检查纯视频 setInterval(doVideos, 1000); } const rootDiv = document.createElement("div"); rootDiv.style = ` position: fixed; z-index: 9999999999999999; opacity: 0.9; `; rootDiv.style.display = "none"; document.documentElement.appendChild(rootDiv); const shadowDOM = rootDiv.attachShadow({ mode: 'open' }); const wrapper = document.createElement("div"); shadowDOM.appendChild(wrapper); // 指示器 const bar = document.createElement("div"); bar.style = ` text-align: right; `; bar.innerHTML = ` `; wrapper.appendChild(bar); // 样式 const style = document.createElement("style"); style.innerHTML = ` .number-indicator{ position:relative; } .number-indicator::after{ content: attr(data-number); position: absolute; bottom: 0; right: 0; color: #40a9ff; font-size: 14px; font-weight: bold; background: #000; border-radius: 10px; padding: 3px 5px; } .copy-link:link{ text-decoration: none; } .copy-link:hover{ text-decoration: underline; } .download-btn:hover{ text-decoration: underline; } .download-btn:active{ opacity: 0.9; } .m3u8-item{ color: white; margin-bottom: 5px; display: flex; flex-direction: row; align-items: baseline; background: black; padding: 3px 10px; border-radius: 3px; font-size: 12px; user-select: none; } [data-shown="false"] { opacity: 0.8; zoom: 0.8; } [data-shown="false"]:hover{ opacity: 1; } [data-shown="false"] .m3u8-item{ display: none; } `; wrapper.appendChild(style); const barBtn = bar.querySelector(".number-indicator"); // 关于显隐和移动 (async function () { let shown = await GM_getValue("shown", true); wrapper.setAttribute("data-shown", shown); let x = await GM_getValue("x", 10); let y = await GM_getValue("y", 10); x = Math.min(innerWidth - 50, x); y = Math.min(innerHeight - 50, y); if (x < 0) x = 0; if (y < 0) y = 0; rootDiv.style.top = `${y}px`; rootDiv.style.right = `${x}px`; barBtn.addEventListener("mousedown", e => { let startX = e.pageX; let startY = e.pageY; let moved = false; let mousemove = e => { let offsetX = e.pageX - startX; let offsetY = e.pageY - startY; if (moved || (Math.abs(offsetX) + Math.abs(offsetY)) > 5) { moved = true; rootDiv.style.top = `${y + offsetY}px`; rootDiv.style.right = `${x - offsetX}px`; } }; let mouseup = e => { let offsetX = e.pageX - startX; let offsetY = e.pageY - startY; if (moved) { x -= offsetX; y += offsetY; mgmapi.setValue("x", x); mgmapi.setValue("y", y); } else { shown = !shown; mgmapi.setValue("shown", shown); wrapper.setAttribute("data-shown", shown); } removeEventListener("mousemove", mousemove); removeEventListener("mouseup", mouseup); } addEventListener("mousemove", mousemove); addEventListener("mouseup", mouseup); }); })(); let count = 0; let shownUrls = []; if (window.top !== window.self) { showVideo({ type: "iframe", url: new URL(location.href), duration: "unknown", async download() { mgmapi.openInTab( `https://tools.thatwind.com/tool/m3u8downloader#${new URLSearchParams({ m3u8: url.href, referer: location.href, filename: (await getTopTitle()) || "" })}` ); } }); } function doVideos() { for (let v of Array.from(document.querySelectorAll("video"))) { if (v.duration && v.src && v.src.startsWith("http") && (!shownUrls.includes(v.src))) { const src = v.src; shownUrls.push(src); showVideo({ type: "video", url: new URL(src), duration: `${Math.ceil(v.duration * 10 / 60) / 10} mins`, download() { const details = { url: src, name: (() => { let name = new URL(src).pathname.split("/").slice(-1)[0]; if (!/\.\w+$/.test(name)) { if (name.match(/^\s*$/)) name = Date.now(); name = name + ".mp4"; } return name; })(), headers: { // referer: location.origin, // 不允许该头 origin: location.origin }, onerror(e) { mgmapi.openInTab(src); } }; mgmapi.download(details); } }) } } } async function doM3U({ url, content }) { url = new URL(url); if (shownUrls.includes(url.href)) return; // 解析 m3u content = content || await (await fetch(url)).text(); const parser = new m3u8Parser.Parser(); parser.push(content); parser.end(); const manifest = parser.manifest; if (manifest.segments) { let duration = 0; manifest.segments.forEach((segment) => { duration += segment.duration; }); manifest.duration = duration; } showVideo({ type: "m3u8", url, duration: manifest.duration ? `${Math.ceil(manifest.duration * 10 / 60) / 10} mins` : manifest.playlists ? `多(Multi)(${manifest.playlists.length})` : "未知(unknown)", async download() { mgmapi.openInTab( `https://tools.thatwind.com/tool/m3u8downloader#${new URLSearchParams({ m3u8: url.href, referer: location.href, filename: (await getTopTitle()) || "" })}` ); } }) } async function showVideo({ type, url, duration, download }) { let div = document.createElement("div"); div.className = "m3u8-item"; div.innerHTML = ` ${type} ${url.pathname} ${duration} `; div.querySelector(".copy-link").addEventListener("click", () => { // 复制链接 mgmapi.copyText(url.href); mgmapi.message("已复制链接 (link copied)", 2000); }); div.querySelector(".download-btn").addEventListener("click", download); rootDiv.style.display = "block"; count++; shownUrls.push(url.href); bar.querySelector(".number-indicator").setAttribute("data-number", count); wrapper.appendChild(div); } })(); /* (function () { 'use strict'; const reg = /magnet:\?xt=urn:btih:\w{10,}([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/; let l = navigator.language || "en"; if (l.startsWith("en-")) l = "en"; else if (l.startsWith("zh-")) l = "zh-CN"; else l = "en"; const T = { "en": { play: "Play" }, "zh-CN": { play: '播放' } }[l]; whenDOMReady(() => { addStyle(` button[data-wtmzjk-mag-url]{ all: initial; border: none; outline: none; background: none; background: #f7d308; background: #08a6f7; margin: 2px 8px; border-radius: 3px; color: white; cursor: pointer; display: inline-flex; height: 1.6em; padding: 0 .8em; align-items: center; justify-content: center; transition: background .15s; text-decoration: none; border-radius: 0.8em; font-size: small; } button[data-wtmzjk-mag-url]>svg{ height: 60%; fill: white; pointer-events: none; } button[data-wtmzjk-mag-url]:hover{ background: #fae157; background: #39b9f9; } button[data-wtmzjk-mag-url]:active{ background: #dfbe07; background: #0797df; } button[data-wtmzjk-mag-url]>span{ pointer-events: none; font-size: small;margin-right: .5em;font-weight:bold;color:white !important; } `); window.addEventListener("click", onEvents, true); window.addEventListener("mousedown", onEvents, true); window.addEventListener("mouseup", onEvents, true); watchBodyChange(work); }); function onEvents(e) { if (e.target.hasAttribute('data-wtmzjk-mag-url')) { e.preventDefault(); e.stopPropagation(); if (e.type == "click") { let a = document.createElement('a'); a.href = 'https://www.diancigaoshou.com/#' + new URLSearchParams({ url: e.target.getAttribute('data-wtmzjk-mag-url') }); a.target = "_blank"; a.click(); } } } function createWatchButton(url, isForPlain = false) { let button = document.createElement("button"); button.setAttribute('data-wtmzjk-mag-url', url); if (isForPlain) button.setAttribute('data-wtmzjk-button-for-plain', ''); button.innerHTML = `${T.play}`; return button; } function hasPlainMagUrlThatNotHandled() { let m = document.body.textContent.match(new RegExp(reg, 'g')); return document.querySelectorAll(`[data-wtmzjk-button-for-plain]`).length != (m ? m.length : 0); } function work() { if (!document.body) return; if (hasPlainMagUrlThatNotHandled()) { for (let node of getAllTextNodes(document.body)) { if (node.nextSibling && node.nextSibling.hasAttribute && node.nextSibling.hasAttribute('data-wtmzjk-mag-url')) continue; let text = node.nodeValue; if (!reg.test(text)) continue; let match = text.match(reg); if (match) { let url = match[0]; let p = node.parentNode; p.insertBefore(document.createTextNode(text.slice(0, match.index + url.length)), node); p.insertBefore(createWatchButton(url, true), node); p.insertBefore(document.createTextNode(text.slice(match.index + url.length)), node); p.removeChild(node); } } } for (let a of Array.from(document.querySelectorAll( ['href', 'value', 'data-clipboard-text', 'data-value', 'title', 'alt', 'data-url', 'data-magnet', 'data-copy'].map(n => `[${n}*="magnet:?xt=urn:btih:"]`).join(',') ))) { if (a.nextSibling && a.nextSibling.hasAttribute && a.nextSibling.hasAttribute('data-wtmzjk-mag-url')) continue; // 已经添加 if (reg.test(a.textContent)) continue; for (let attr of a.getAttributeNames()) { let val = a.getAttribute(attr); if (!reg.test(val)) continue; let url = val.match(reg)[0]; a.parentNode.insertBefore(createWatchButton(url), a.nextSibling); } } } function watchBodyChange(onchange) { let timeout; let observer = new MutationObserver(() => { if (!timeout) { timeout = setTimeout(() => { timeout = null; onchange(); }, 200); } }); observer.observe(document.documentElement, { childList: true, subtree: true, attributes: true, characterData: true }); } function getAllTextNodes(parent) { var re = []; if (["STYLE", "SCRIPT", "BASE", "COMMAND", "LINK", "META", "TITLE", "XTRANS-TXT", "XTRANS-TXT-GROUP", "XTRANS-POPUP"].includes(parent.tagName)) return re; for (let node of parent.childNodes) { if (node.childNodes.length) re = re.concat(getAllTextNodes(node)); else if (Text.prototype.isPrototypeOf(node) && (!node.nodeValue.match(/^\s*$/))) re.push(node); } return re; } function whenDOMReady(f) { if (document.body) f(); else window.addEventListener("DOMContentLoaded", f); } function addStyle(s) { let style = document.createElement("style"); style.innerHTML = s; document.documentElement.appendChild(style); } })(); */