// ==UserScript== // @name MPV-M3U8 Video Detector and Downloader (Ultra-Compact with Index) // @name:en MPV-M3U8 Video Detector and Downloader // @version 1.7.5 // @description:en Automatically detect the m3u8 video of the page with sequence index. Optimized for drag-and-drop integration with Handlers Helper to stream directly to MPV. // @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.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 { mdiv.style.opacity = '0'; mdiv.style.transition = 'opacity 0.2s ease'; setTimeout(() => p.removeChild(mdiv), 200); }, disappearTime); } }; if (location.host === "tools.thatwind.com" || location.host === "localhost:3000") { mgmapi.addStyle("#userscript-tip{display:none !important;}"); return; } // Lắng nghe bắt link m3u8 / flv { if (location.href.match(/^.*?socolive.*?$/)) { var sfetch = unsafeWindow.fetch; unsafeWindow.fetch = new Proxy(sfetch, { apply: function(target, thisArg, args) { if (args[0].indexOf(".flv") != -1) doM3U({ url: args[0], content: args[0] }); return Reflect.apply(target, thisArg, args); } }); } 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); return (url.pathname.indexOf(".m3u8") != -1 || url.pathname.indexOf(".m3u") != -1); } function checkContent(content) { return content.trim().startsWith("#EXTM3U"); } setInterval(doVideos, 1000); } // Tạo Khung chứa Shadow DOM ẩn / hiện const rootDiv = document.createElement("div"); rootDiv.style = "position:fixed;z-index:9999999999999999;transition:opacity 0.2s ease;display:none;"; document.documentElement.appendChild(rootDiv); const shadowDOM = rootDiv.attachShadow({ mode: 'open' }); const wrapper = document.createElement("div"); wrapper.id = "m3u8-main-wrapper"; shadowDOM.appendChild(wrapper); // Chỉ thị nút nổi FAB tròn (Chỉ chứa Số lượng đếm) const bar = document.createElement("div"); bar.className = "fab-container"; bar.innerHTML = `
🚀
`; wrapper.appendChild(bar); // CSS Giao diện siêu tối giản (Hỗ trợ kéo thả link tự nhiên) const style = document.createElement("style"); style.innerHTML = ` #m3u8-main-wrapper { display: flex; flex-direction: column; align-items: flex-end; gap: 6px; padding: 6px; font-family: system-ui, -apple-system, sans-serif; } .fab-container { display: flex; justify-content: flex-end; width: 100%; } .number-indicator { position: relative; display: flex; align-items: center; justify-content: center; width: 32px; height: 32px; background: linear-gradient(135deg, #4f46e5, #7c3aed); border-radius: 50%; box-shadow: 0 3px 10px rgba(79, 70, 229, 0.4); cursor: move; font-size: 14px; user-select: none; transition: transform 0.2s; } .number-indicator:hover { transform: scale(1.05); } .number-indicator::after { content: attr(data-number); position: absolute; top: -4px; right: -4px; background: #ef4444; color: #fff; font-size: 9px; font-weight: bold; padding: 1px 4px; border-radius: 8px; min-width: 10px; text-align: center; box-shadow: 0 1px 3px rgba(0,0,0,0.3); } /* Danh sách Link dạng thẻ thuần để tương thích tính năng KÉO THẢ của Handlers Helper */ .m3u8-item-link { display: block; background: rgba(20, 20, 22, 0.85); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); border: 1px solid rgba(255, 255, 255, 0.08); border-radius: 6px; padding: 6px 10px; color: #f1f5f9; font-size: 12px; text-decoration: none; max-width: 280px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); cursor: grab; user-select: none; transform-origin: right center; animation: itemIn 0.2s ease forwards; } .m3u8-item-link:hover { background: rgba(35, 35, 40, 0.95); border-color: #4f46e5; color: #818cf8; } .m3u8-item-link:active { cursor: grabbing; } /* Style cho Số thứ tự Index */ .item-index { color: #64748b; font-weight: bold; margin-right: 4px; } @keyframes itemIn { from { opacity: 0; transform: translateX(15px); } to { opacity: 1; transform: translateX(0); } } [data-shown="false"] .m3u8-item-link { display: none; } [data-shown="false"] .number-indicator { opacity: 0.5; } `; wrapper.appendChild(style); // Xử lý di chuyển & ẩn hiện nút nổi 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", 20); let y = await GM_getValue("y", 20); x = Math.min(innerWidth - 40, x); y = Math.min(innerHeight - 40, 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 = []; function doVideos() { for (let v of Array.from(document.querySelectorAll("video"))) { if (v.duration && v.src && v.src.startsWith("http") && (!shownUrls.includes(v.src))) { showVideo({ type: "Video", url: new URL(v.src) }); } } } async function doM3U({ url, content }) { url = new URL(url); if (shownUrls.includes(url.href)) return; showVideo({ type: "M3U8", url }); } // Hiển thị danh sách kèm STT (Index) tăng dần theo thời gian nhận link async function showVideo({ type, url }) { if (shownUrls.includes(url.href)) return; shownUrls.push(url.href); count++; let a = document.createElement("a"); a.className = "m3u8-item-link"; a.href = url.href; let fileName = url.pathname.split("/").pop() || "Media Link"; // Chèn tag số thứ tự #count vào trước text link a.innerHTML = `#${count} [${type}] ${fileName}`; a.title = `[Thứ tự lấy: ${count}]\nGiữ chuột kéo thẻ này để chuyển tiếp sang MPV\nClick chuột trái để Copy Link.`; // Click chuột trái hỗ trợ nhanh tính năng Copy link a.addEventListener("click", (e) => { e.preventDefault(); mgmapi.copyText(url.href); mgmapi.message(`Đã copy link #${count} ✔`, 2000); }); rootDiv.style.display = "block"; bar.querySelector(".number-indicator").setAttribute("data-number", count); wrapper.appendChild(a); } })();