您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
自动检测页面m3u8视频并进行完整下载。修复了URL编码问题,确保PotPlayer正确调用。
// ==UserScript== // @name m3u8视频侦测下载,调用本地potplayer播放器,调用以后就可以实现VR格式的视频直接3D转2D // @name:zh-CN m3u8视频侦测下载器 // @name:zh-TW m3u8視頻偵測下載器 // @name:en M3U8 Video Detector and Downloader // @version 1.5.2 // @description 自动检测页面m3u8视频并进行完整下载。修复了URL编码问题,确保PotPlayer正确调用。 // @description:zh-CN 自动检测页面m3u8视频并进行完整下载。修复了URL编码问题,确保PotPlayer正确调用。 // @description:zh-TW 自動檢測頁面m3u8視頻並進行完整下載。修復了URL編碼問題,確保PotPlayer正確調用。 // @description:en Automatically detect the m3u8 video of the page and download it completely. Fixed URL encoding issue to ensure PotPlayer works correctly. // @icon https://tools.thatwind.com/favicon.png // @author allFull // @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 // ==/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); }, 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); } }; // 播放器配置 - 修复URL编码问题 const players = { "vlc": { name: "VLC Player", protocol: "vlc://", // VLC需要编码的URL format: (url) => `vlc://${encodeURIComponent(url)}` }, "potplayer": { name: "PotPlayer", protocol: "potplayer://", // PotPlayer需要未编码的原始URL format: (url) => `potplayer://${url}` }, "mpc-hc": { name: "MPC-HC", protocol: "mpc-hc://", format: (url) => `mpc-hc://open/file?url=${encodeURIComponent(url)}` }, "mpv": { name: "MPV", protocol: "mpv://", format: (url) => `mpv://${url}` } }; // 获取默认播放器 - 默认使用PotPlayer async function getDefaultPlayer() { const defaultPlayer = await mgmapi.getValue("defaultPlayer", "potplayer"); return players[defaultPlayer] ? defaultPlayer : "potplayer"; } // 设置默认播放器 async function setDefaultPlayer(playerId) { if (players[playerId]) { await mgmapi.setValue("defaultPlayer", playerId); return true; } return false; } // 调用本地播放器 - 确保URL正确处理 function launchLocalPlayer(url) { // 确保URL是解码后的原始格式 let decodedUrl = url; try { // 检查是否已编码,如果是则解码 if (decodedUrl !== decodeURIComponent(decodedUrl)) { decodedUrl = decodeURIComponent(decodedUrl); } } catch (e) { console.log("URL无需解码或解码失败,使用原始URL:", e); } getDefaultPlayer().then(defaultPlayer => { try { const player = players[defaultPlayer]; const playerUrl = player.format(decodedUrl); // 创建一个隐藏的链接并点击它来调用本地播放器 const link = document.createElement('a'); link.href = playerUrl; link.style.display = 'none'; document.body.appendChild(link); link.click(); document.body.removeChild(link); mgmapi.message(`正在使用${player.name}播放...\nPlaying with ${player.name}...`, 3000); } catch (e) { mgmapi.message(`调用播放器失败,请确保已安装${players[defaultPlayer].name}\nFailed to launch player. Please ensure ${players[defaultPlayer].name} is installed.`, 5000); console.error("播放器调用失败:", e); } }); } // 添加播放器设置按钮 function addPlayerSettings() { const settingsDiv = document.createElement("div"); settingsDiv.className = "player-settings"; settingsDiv.style = ` color: white; padding: 5px 10px; background: #333; border-radius: 3px; font-size: 12px; margin-bottom: 5px; `; let settingsHtml = "<span>默认播放器 (Default Player): </span>"; Object.keys(players).forEach(playerId => { settingsHtml += ` <span class="player-option ${playerId}" style="margin: 0 3px; cursor: pointer; padding: 2px 5px; border-radius: 2px;" data-player="${playerId}" > ${players[playerId].name} </span> `; }); settingsDiv.innerHTML = settingsHtml; wrapper.appendChild(settingsDiv); // 设置默认选中状态 getDefaultPlayer().then(defaultPlayer => { document.querySelector(`.player-option.${defaultPlayer}`).style.background = "#666"; }); // 绑定播放器选择事件 document.querySelectorAll(".player-option").forEach(option => { option.addEventListener("click", function() { const playerId = this.getAttribute("data-player"); setDefaultPlayer(playerId).then(success => { if (success) { document.querySelectorAll(".player-option").forEach(opt => { opt.style.background = ""; }); this.style.background = "#666"; mgmapi.message(`默认播放器已设置为${players[playerId].name}\nDefault player set to ${players[playerId].name}`, 2000); } }); }); }); } 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)); resolve(await mgmapi.getValue(name)); mgmapi.deleteValue(name); window.removeEventListener("message", l); } } }); window.top.postMessage("3j4t9uj349-gm-get-title", "*"); }); } { // 请求检测 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 }); }).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 }); } catch { } }); return _open.apply(this, args); } function checkUrl(url) { url = new URL(url, location.href); if (url.pathname.endsWith(".m3u8") || url.pathname.endsWith(".m3u")) { 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 = ` <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> `; wrapper.appendChild(bar); // 添加播放器设置 addPlayerSettings(); // 样式 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, .local-play-btn:hover{ text-decoration: underline; } .download-btn:active, .local-play-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; align-items: center; flex-wrap: wrap; } [data-shown="false"] { opacity: 0.8; zoom: 0.8; } [data-shown="false"]:hover{ opacity: 1; } [data-shown="false"] .m3u8-item, [data-shown="false"] .player-settings { display: none; } .local-play-btn { margin-left: 10px; cursor: pointer; color: #40a9ff; white-space: nowrap; } .player-settings { user-select: none; } .player-option:hover { background-color: #555; } .player-option.potplayer { background-color: #666; } `; wrapper.appendChild(style); const barBtn = bar.querySelector(".number-indicator"); // 关于显隐和移动 (async function () { let shown = await mgmapi.getValue("shown", true); wrapper.setAttribute("data-shown", shown); let x = await mgmapi.getValue("x", 10); let y = await mgmapi.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 = []; 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: { origin: location.origin }, onerror(e) { mgmapi.openInTab(src); } }; mgmapi.download(details); }, playLocally() { launchLocalPlayer(src); } }) } } } 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()) || "" })}` ); }, playLocally() { launchLocalPlayer(url.href); } }) } async function showVideo({ type, url, duration, download, playLocally }) { 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; white-space: nowrap; " >${duration}</span> <span class="local-play-btn" style=" " >本地播放(Local)</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); div.querySelector(".local-play-btn").addEventListener("click", playLocally); 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", localPlay: "Local Play (PotPlayer)" }, "zh-CN": { play: '播放', localPlay: '本地播放 (PotPlayer)' } }[l]; // 播放器配置 - 磁力链接专用,修复URL编码 const magnetPlayers = { "vlc": { name: "VLC Player", protocol: "vlc://", format: (url) => `vlc://${encodeURIComponent(url)}` }, "potplayer": { name: "PotPlayer", protocol: "potplayer://", // PotPlayer磁力链接也使用未编码的URL format: (url) => `potplayer://${url}` } }; // 获取默认磁力链接播放器 async function getMagnetDefaultPlayer() { const defaultPlayer = await GM.getValue("magnetDefaultPlayer", "potplayer"); return magnetPlayers[defaultPlayer] ? defaultPlayer : "potplayer"; } // 调用本地播放器播放磁力链接 - 修复URL编码问题 function launchMagnetLocalPlayer(url) { // 确保URL解码 let decodedUrl = url; try { if (decodedUrl !== decodeURIComponent(decodedUrl)) { decodedUrl = decodeURIComponent(decodedUrl); } } catch (e) { console.log("磁力链接URL无需解码或解码失败:", e); } getMagnetDefaultPlayer().then(defaultPlayer => { try { const player = magnetPlayers[defaultPlayer]; const playerUrl = player.format(decodedUrl); const link = document.createElement('a'); link.href = playerUrl; link.style.display = 'none'; document.body.appendChild(link); link.click(); document.body.removeChild(link); const messageDiv = document.createElement('div'); messageDiv.textContent = `正在使用PotPlayer播放...`; messageDiv.style = ` position: fixed; bottom: 20px; right: 20px; padding: 8px 12px; background: #333; color: white; border-radius: 4px; z-index: 999999; `; document.body.appendChild(messageDiv); setTimeout(() => { messageDiv.remove(); }, 3000); } catch (e) { console.error("磁力链接播放器调用失败:", e); const errorDiv = document.createElement('div'); errorDiv.textContent = `调用PotPlayer失败,请确保已安装PotPlayer并正确配置`; errorDiv.style = ` position: fixed; bottom: 20px; right: 20px; padding: 8px 12px; background: #ff4444; color: white; border-radius: 4px; z-index: 999999; `; document.body.appendChild(errorDiv); setTimeout(() => { errorDiv.remove(); }, 5000); } }); } 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-local]{ all: initial; border: none; outline: none; background: none; background: #4CAF50; 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, button[data-wtmzjk-mag-local]>svg{ height: 60%; fill: white; pointer-events: none; } button[data-wtmzjk-mag-url]:hover{ background: #39b9f9; } button[data-wtmzjk-mag-local]:hover{ background: #45a049; } button[data-wtmzjk-mag-url]:active{ background: #0797df; } button[data-wtmzjk-mag-local]:active{ background: #3d8b40; } button[data-wtmzjk-mag-url]>span, button[data-wtmzjk-mag-local]>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(); } } else if (e.target.hasAttribute('data-wtmzjk-mag-local')) { e.preventDefault(); e.stopPropagation(); if (e.type == "click") { launchMagnetLocalPlayer(e.target.getAttribute('data-wtmzjk-mag-local')); } } } function createWatchButton(url, isForPlain = false) { let onlineButton = document.createElement("button"); onlineButton.setAttribute('data-wtmzjk-mag-url', url); if (isForPlain) onlineButton.setAttribute('data-wtmzjk-button-for-plain', ''); onlineButton.innerHTML = `<span>${T.play}</span><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--! Font Awesome Pro 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><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>`; let localButton = document.createElement("button"); localButton.setAttribute('data-wtmzjk-mag-local', url); if (isForPlain) localButton.setAttribute('data-wtmzjk-button-for-plain', ''); localButton.innerHTML = `<span>${T.localPlay}</span><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--! Font Awesome Pro 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M192 0C85.96 0 0 85.96 0 192s85.96 192 192 192 192-85.96 192-192S298 0 192 0zm0 352c-88.22 0-160-71.78-160-160S103.8 32 192 32s160 71.78 160 160-71.78 160-160 160zm-16-192c0-8.84 7.16-16 16-16h48c8.84 0 16 7.16 16 16v64c0 8.84-7.16 16-16 16h-48c-8.84 0-16-7.16-16-16V160z"/></svg>`; let container = document.createElement("span"); container.appendChild(onlineButton); container.appendChild(localButton); return container; } 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); } })();