Last active
September 10, 2025 12:16
-
-
Save Tuurash/ea147102ef311d9e99ebdc1f0c913eca to your computer and use it in GitHub Desktop.
Enhancing greasyfork.org/scripts/536252. 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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name m3u8Copyr | |
| // @name:en m3u8Copyr | |
| // @version 1.0.1 | |
| // @description Enhancing greasyfork.org/scripts/536252. 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 tuurash | |
| // @namespace https://tools.thatwind.com/ | |
| // @homepage https://tools.thatwind.com/tool/m3u8downloader | |
| // @match *://*/* | |
| // @exclude *://www.diancigaoshou.com/* | |
| // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/m3u8-parser.min.js | |
| // @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 | |
| // @downloadURL https://gist.github.com/Tuurash/ea147102ef311d9e99ebdc1f0c913eca/raw/0c478260e9353f5f1ce3e4720697d7efea3da2eb/m3u8Copyr.user.js | |
| // @updateURL https://gist.github.com/Tuurash/ea147102ef311d9e99ebdc1f0c913eca/raw/0c478260e9353f5f1ce3e4720697d7efea3da2eb/m3u8Copyr.meta.js | |
| // ==/UserScript== | |
| (function () { | |
| 'use strict'; | |
| const mgmapi = { | |
| addStyle(s) { | |
| let style = document.createElement("style"); | |
| style.innerHTML = s; | |
| document.documentElement.appendChild(style); | |
| }, | |
| async getValue(name, defaultVal) { | |
| return await ((typeof GM_getValue === "function") ? GM_getValue : GM.getValue)(name, defaultVal); | |
| }, | |
| async setValue(name, value) { | |
| return await ((typeof GM_setValue === "function") ? GM_setValue : GM.setValue)(name, value); | |
| }, | |
| async deleteValue(name) { | |
| return await ((typeof GM_deleteValue === "function") ? GM_deleteValue : GM.deleteValue)(name); | |
| }, | |
| openInTab(url, open_in_background = false) { | |
| return ((typeof GM_openInTab === "function") ? GM_openInTab : GM.openInTab)(url, open_in_background); | |
| }, | |
| xmlHttpRequest(details) { | |
| return ((typeof GM_xmlhttpRequest === "function") ? GM_xmlhttpRequest : GM.xmlHttpRequest)(details); | |
| }, | |
| download(details) { | |
| return this.openInTab(details.url); | |
| if (typeof GM_download === "function") { | |
| this.message("Downloading, pay attention to the browser's download pop-up.", 3000); | |
| return GM_download(details); | |
| } else { | |
| this.openInTab(details.url); | |
| } | |
| }, | |
| copyText(text) { | |
| copyTextToClipboard(text); | |
| function copyTextToClipboard(text) { | |
| var copyFrom = document.createElement("textarea"); | |
| copyFrom.textContent = text; | |
| document.body.appendChild(copyFrom); | |
| copyFrom.select(); | |
| document.execCommand('copy'); | |
| copyFrom.blur(); | |
| document.body.removeChild(copyFrom); | |
| } | |
| }, | |
| message(text, disappearTime = 5000) { | |
| const id = "f8243rd238-gm-message-panel"; | |
| let p = document.querySelector(`#${id}`); | |
| if (!p) { | |
| p = document.createElement("div"); | |
| p.id = id; | |
| p.style = ` | |
| position: fixed; | |
| bottom: 20px; | |
| right: 20px; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: end; | |
| z-index: 999999999999999; | |
| `; | |
| (document.body || document.documentElement).appendChild(p); | |
| } | |
| let mdiv = document.createElement("div"); | |
| mdiv.innerText = text; | |
| mdiv.style = ` | |
| padding: 3px 8px; | |
| border-radius: 5px; | |
| background: black; | |
| box-shadow: #000 1px 2px 5px; | |
| margin-top: 10px; | |
| font-size: small; | |
| color: #fff; | |
| text-align: right; | |
| `; | |
| p.appendChild(mdiv); | |
| setTimeout(() => { | |
| 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(`Proxy request: ${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; | |
| } | |
| 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)); | |
| resolve(await mgmapi.getValue(name)); | |
| mgmapi.deleteValue(name); | |
| window.removeEventListener("message", l); | |
| } | |
| } | |
| }); | |
| window.top.postMessage("3j4t9uj349-gm-get-title", "*"); | |
| }); | |
| } | |
| 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 = ` | |
| <span | |
| class="number-indicator" | |
| data-number="0" | |
| style=" | |
| display: inline-flex; | |
| width: 25px; | |
| height: 25px; | |
| background: black; | |
| padding: 10px; | |
| border-radius: 100px; | |
| margin-bottom: 5px; | |
| cursor: pointer; | |
| border: 3px solid #83838382; | |
| " | |
| > | |
| <svg | |
| style=" | |
| filter: invert(1); | |
| " | |
| version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 585.913 585.913" style="enable-background:new 0 0 585.913 585.913;" xml:space="preserve"> | |
| <g> | |
| <path d="M11.173,46.2v492.311l346.22,47.402V535.33c0.776,0.058,1.542,0.109,2.329,0.109h177.39 | |
| c20.75,0,37.627-16.883,37.627-37.627V86.597c0-20.743-16.877-37.628-37.627-37.628h-177.39c-0.781,0-1.553,0.077-2.329,0.124V0 | |
| L11.173,46.2z M110.382,345.888l-1.37-38.273c-0.416-11.998-0.822-26.514-0.822-41.023l-0.415,0.01 | |
| c-2.867,12.767-6.678,26.956-10.187,38.567l-10.961,38.211l-15.567-0.582l-9.239-37.598c-2.801-11.269-5.709-24.905-7.725-37.361 | |
| l-0.25,0.005c-0.503,12.914-0.879,27.657-1.503,39.552L50.84,343.6l-17.385-0.672l5.252-94.208l25.415-0.996l8.499,32.064 | |
| c2.724,11.224,5.467,23.364,7.428,34.819h0.389c2.503-11.291,5.535-24.221,8.454-35.168l9.643-33.042l27.436-1.071l5.237,101.377 | |
| L110.382,345.888z M172.479,349.999c-12.569-0.504-23.013-4.272-28.539-8.142l4.504-17.249c3.939,2.226,13.1,6.445,22.373,6.687 | |
| c12.009,0.32,18.174-5.497,18.174-13.218c0-10.068-9.838-14.683-19.979-14.74l-9.253-0.052v-16.777l8.801-0.066 | |
| c7.708-0.208,17.646-3.262,17.646-11.905c0-6.121-4.914-10.562-14.635-10.331c-7.95,0.189-16.245,3.914-20.213,6.446l-4.52-16.693 | |
| c5.693-4.008,17.224-8.11,29.883-8.588c21.457-0.795,33.643,10.407,33.643,24.625c0,11.029-6.197,19.691-18.738,24.161v0.314 | |
| c12.229,2.216,22.266,11.663,22.266,25.281C213.89,338.188,197.866,351.001,172.479,349.999z M331.104,302.986 | |
| c0,36.126-19.55,52.541-51.193,51.286c-29.318-1.166-46.019-17.103-46.019-52.044v-61.104l25.711-1.006v64.201 | |
| c0,19.191,7.562,29.146,21.179,29.502c14.234,0.368,22.189-8.976,22.189-29.26v-66.125l28.122-1.097v65.647H331.104z | |
| M359.723,70.476h177.39c8.893,0,16.125,7.236,16.125,16.126v411.22c0,8.888-7.232,16.127-16.125,16.127h-177.39 | |
| c-0.792,0-1.563-0.116-2.329-0.232V380.782c17.685,14.961,40.504,24.032,65.434,24.032c56.037,0,101.607-45.576,101.607-101.599 | |
| c0-56.029-45.581-101.603-101.607-101.603c-24.93,0-47.749,9.069-65.434,24.035V70.728 | |
| C358.159,70.599,358.926,70.476,359.723,70.476z M390.873,364.519V245.241c0-1.07,0.615-2.071,1.586-2.521 | |
| c0.981-0.483,2.13-0.365,2.981,0.307l93.393,59.623c0.666,0.556,1.065,1.376,1.065,2.215c0,0.841-0.399,1.67-1.065,2.215 | |
| l-93.397,59.628c-0.509,0.4-1.114,0.61-1.743,0.61l-1.233-0.289C391.488,366.588,390.873,365.585,390.873,364.519z" /> | |
| </g> | |
| </svg> | |
| </span> | |
| <button class="minimize-btn" style="margin-left: 8px; padding: 2px 8px; border-radius: 5px; border: none; background: #888; color: #fff; cursor: pointer; font-size: 13px;">−</button> | |
| <button class="copy-m3u8-btn" style="margin-left: 8px; padding: 2px 8px; border-radius: 5px; border: none; background: #40a9ff; color: #fff; cursor: pointer; font-size: 13px;">Copy m3u8/Video Link</button> | |
| <button class="refresh-page-btn" style="margin-left: 8px; padding: 2px 8px; border-radius: 5px; border: none; background: #ff9800; color: #fff; cursor: pointer; font-size: 13px;">Refresh Page</button> | |
| `; | |
| 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:active{ | |
| color: #ccc; | |
| } | |
| .download-btn:hover{ | |
| text-decoration: underline; | |
| } | |
| .download-btn:active{ | |
| opacity: 0.9; | |
| } | |
| .m3u8-item{ | |
| color: white; | |
| margin-bottom: 5px; | |
| display: flex; | |
| flex-direction: row; | |
| background: black; | |
| padding: 3px 10px; | |
| border-radius: 3px; | |
| font-size: 14px; | |
| 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"); | |
| const minimizeBtn = bar.querySelector('.minimize-btn'); | |
| const copyBtn = bar.querySelector('.copy-m3u8-btn'); | |
| const refreshBtn = bar.querySelector('.refresh-page-btn'); | |
| let lastDetectedMediaUrl = null; | |
| let minimized = false; | |
| (async function() { | |
| minimized = await GM_getValue('minimized', false); | |
| setMinimizedUI(minimized); | |
| })(); | |
| function setMinimizedUI(min) { | |
| minimized = min; | |
| GM_setValue('minimized', min); | |
| if (min) { | |
| // Hide all except number-indicator and show a small expand button | |
| for (const el of bar.children) { | |
| if (!el.classList.contains('number-indicator')) el.style.display = 'none'; | |
| } | |
| let expandBtn = bar.querySelector('.expand-btn'); | |
| if (!expandBtn) { | |
| expandBtn = document.createElement('button'); | |
| expandBtn.className = 'expand-btn'; | |
| expandBtn.textContent = '+'; | |
| expandBtn.style = 'margin-left: 8px; padding: 2px 8px; border-radius: 5px; border: none; background: #888; color: #fff; cursor: pointer; font-size: 13px;'; | |
| bar.appendChild(expandBtn); | |
| expandBtn.addEventListener('click', function(e) { | |
| e.stopPropagation(); | |
| setMinimizedUI(false); | |
| }); | |
| } else { | |
| expandBtn.style.display = ''; | |
| } | |
| } else { | |
| for (const el of bar.children) { | |
| el.style.display = ''; | |
| } | |
| const expandBtn = bar.querySelector('.expand-btn'); | |
| if (expandBtn) expandBtn.style.display = 'none'; | |
| } | |
| } | |
| minimizeBtn.addEventListener('click', function(e) { | |
| e.stopPropagation(); | |
| setMinimizedUI(true); | |
| }); | |
| (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); | |
| }); | |
| })(); | |
| copyBtn.addEventListener('click', function() { | |
| if (lastDetectedMediaUrl) { | |
| mgmapi.copyText(lastDetectedMediaUrl); | |
| mgmapi.message('Link copied', 2000); | |
| } else { | |
| mgmapi.message('No m3u8/video URL detected', 2000); | |
| } | |
| }); | |
| refreshBtn.addEventListener('click', function() { | |
| location.reload(); | |
| }); | |
| 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))) { | |
| const src = v.src; | |
| shownUrls.push(src); | |
| lastDetectedMediaUrl = 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: { | |
| 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; | |
| 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()) || "" | |
| })}` | |
| ); | |
| } | |
| }) | |
| lastDetectedMediaUrl = url.href; | |
| } | |
| async function showVideo({ | |
| type, | |
| url, | |
| duration, | |
| download | |
| }) { | |
| let div = document.createElement("div"); | |
| div.className = "m3u8-item"; | |
| div.innerHTML = ` | |
| <span>${type}</span> | |
| <span | |
| class="copy-link" | |
| title="${url}" | |
| style=" | |
| max-width: 200px; | |
| text-overflow: ellipsis; | |
| white-space: nowrap; | |
| overflow: hidden; | |
| margin-left: 10px; | |
| " | |
| >${url.pathname}</span> | |
| <span | |
| style=" | |
| margin-left: 10px; | |
| flex-grow: 1; | |
| " | |
| >${duration}</span> | |
| <span | |
| class="download-btn" | |
| style=" | |
| margin-left: 10px; | |
| cursor: pointer; | |
| ">Download</span> | |
| `; | |
| 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); | |
| } | |
| })(); | |
| // Keyboard Shortcuts handler | |
| window.addEventListener('keydown', function(e) { | |
| if (e.ctrlKey && e.altKey && !e.shiftKey) { | |
| if (e.code === 'KeyM') { | |
| setMinimizedUI(!minimized); | |
| mgmapi.message(minimized ? 'UI minimized' : 'UI expanded', 1500); | |
| e.preventDefault(); | |
| } else if (e.code === 'KeyC') { | |
| if (lastDetectedMediaUrl) { | |
| mgmapi.copyText(lastDetectedMediaUrl); | |
| mgmapi.message('Link copied (shortcut)', 1500); | |
| } else { | |
| mgmapi.message('No m3u8/video URL detected', 1500); | |
| } | |
| e.preventDefault(); | |
| } else if (e.code === 'KeyR') { | |
| mgmapi.message('Refreshing page...', 1000); | |
| setTimeout(() => location.reload(), 300); | |
| e.preventDefault(); | |
| } | |
| } | |
| }); | |
| (function () { | |
| 'use strict'; | |
| const reg = /magnet:\?xt=urn:btih:\w{10,}([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/; | |
| let l = "en"; | |
| const T = { | |
| "en": { | |
| play: "Play" | |
| } | |
| }[l]; | |
| whenDOMReady(() => { | |
| addStyle(` | |
| button[data-wtmzjk-mag-url]{ | |
| all: initial; | |
| border: none; | |
| outline: none; | |
| background: none; | |
| 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: #39b9f9; | |
| } | |
| button[data-wtmzjk-mag-url]:active{ | |
| 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 = `<span>${T.play}</span><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path d="M73 39c-14.8-9.1-33.4-9.4-48.5-.9S0 62.6 0 80V432c0 17.4 9.4 33.4 24.5 41.9s33.7 8.1 48.5-.9L361 297c14.3-8.7 23-24.2 23-41s-8.7-32.2-23-41L73 39z"/></svg>`; | |
| 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); | |
| } | |
| })(); | |
| // Keyboard Shortcuts: | |
| // Ctrl+Alt+M - Minimize/Expand UI | |
| // Ctrl+Alt+C - Copy last detected m3u8/video URL | |
| // Ctrl+Alt+R - Refresh page |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment