// ==UserScript== // @name WeTV 真实播放地址解析 (tv.wetv.vip) // @namespace https://github.com/wetv-parse // @version 1.0.2 // @description 在 WeTV 播放页实时捕获并解析真实 HLS 播放地址 (.m3u8),支持列出全部清晰度与字幕轨;可选通过 getvinfo 接口一次性列出所有清晰度。面板通过页面右下角「WeTV 解析」按钮点击弹出。 // @author WorkBuddy // @match https://tv.wetv.vip/* // @match https://*.wetv.vip/* // @match https://wetv.vip/* // @match https://www.wetv.vip/* // @connect tv.wetv.vip // @connect wetv.vip // @connect play.wetv.vip // @connect *.qpic.cn // @connect static.wetvinfo.com // @grant GM_xmlhttpRequest // @grant GM_addStyle // @grant GM_setClipboard // @run-at document-start // @license MIT // ==/UserScript== (function () { 'use strict'; /* ------------------------------------------------------------------ * * WeTV cKey 算法 (encryptVer 8.1) —— 用于 getvinfo 接口签名 * 来源: Tithen-Firion 的 WeTV cKey 实现 (gist, 2026 仍有效) * ------------------------------------------------------------------ */ var makeCKey = function () { function encodeText(text) { const len = text.length, arr = []; for (let i = 0; i < len; i++) arr[i >>> 2] |= (255 & text.charCodeAt(i)) << 24 - i % 4 * 8; return [arr, len]; } function decodeText(arr, len) { const textArray = []; for (let i = 0; i < len; i++) { textArray.push((arr[i >>> 2] >>> 24 - i % 4 * 8 & 255).toString(16).padStart(2, '0')); } return textArray.join(''); } function shorten(text) { return (text || '').substr(0, 48); } function calculateHash(text) { let result = 0; const len = text.length; for (let i = 0; i < len; i++) result = (result << 5) - result + text.charCodeAt(i); return result; } function padText(text) { const padLength = 16 - text.length % 16; for (let i = 0; i < padLength; ++i) text += String.fromCharCode(padLength); return text; } function encrypt(arr) { const len = arr.length; for (let i = 0; i < len; i += 4) mainAlgorithm(arr, i); } const encryptionArrays = [[1332468387, -1641050960, 2136896045, -1629555948, 1399201960, -850809832, -1307058635, 751381793, -1933648423, 1106735553, -203378700, -550927659, 766369351, 1817882502, -1615200142, 1083409063, -104955314, -1780208184, 173944250, 1254993693, 1422337688, -1054667952, -880990486, -2119136777, -1822404972, 1380140484, -1723964626, 412019417, -890799303, -1734066435, 26893779, 420787978, -1337058067, 686432784, 695238595, 811911369, -391724567, -1068702727, -381903814, -648522509, -1266234148, 1959407397, -1644776673, 1152313324]]; (function () { var d = [], f = [], g = [], h = [], j = [], o = []; for (let i = 0; i < 256; i++) o[i] = i < 128 ? i << 1 : i << 1 ^ 283; var t = 0, u = 0; for (let i = 0; i < 256; i++) { var v = u ^ u << 1 ^ u << 2 ^ u << 3 ^ u << 4; v = v >>> 8 ^ 255 & v ^ 99; d[t] = v; var x = o[t], z = o[o[x]], A = 257 * o[v] ^ 16843008 * v; f[t] = A << 24 | A >>> 8; g[t] = A << 16 | A >>> 16; h[t] = A << 8 | A >>> 24; j[t] = A; t ? (t = x ^ o[o[o[z ^ x]]], u ^= o[o[u]]) : t = u = 1; } encryptionArrays.push(f); encryptionArrays.push(g); encryptionArrays.push(h); encryptionArrays.push(j); encryptionArrays.push(d); })(); function mainAlgorithm(a, b) { const [c, d, e, f, g, h] = encryptionArrays; let xorArr; if (b === 0) { xorArr = [22039283, 1457920463, 776125350, -1941999367]; } else { xorArr = a.slice(b - 4, b); } for (let i = 0; i < 4; i++) a[b + i] ^= xorArr[i]; for (var i = 10, j = a[b] ^ c[0], k = a[b + 1] ^ c[1], l = a[b + 2] ^ c[2], m = a[b + 3] ^ c[3], n = 4, o = 1; o < i; o++) { var q = d[j >>> 24] ^ e[k >>> 16 & 255] ^ f[l >>> 8 & 255] ^ g[255 & m] ^ c[n++], s = d[k >>> 24] ^ e[l >>> 16 & 255] ^ f[m >>> 8 & 255] ^ g[255 & j] ^ c[n++], t = d[l >>> 24] ^ e[m >>> 16 & 255] ^ f[j >>> 8 & 255] ^ g[255 & k] ^ c[n++]; m = d[m >>> 24] ^ e[j >>> 16 & 255] ^ f[k >>> 8 & 255] ^ g[255 & l] ^ c[n++]; j = q; k = s; l = t; } q = (h[j >>> 24] << 24 | h[k >>> 16 & 255] << 16 | h[l >>> 8 & 255] << 8 | h[255 & m]) ^ c[n++]; s = (h[k >>> 24] << 24 | h[l >>> 16 & 255] << 16 | h[m >>> 8 & 255] << 8 | h[255 & j]) ^ c[n++]; t = (h[l >>> 24] << 24 | h[m >>> 16 & 255] << 16 | h[j >>> 8 & 255] << 8 | h[255 & k]) ^ c[n++]; m = (h[m >>> 24] << 24 | h[j >>> 16 & 255] << 16 | h[k >>> 8 & 255] << 8 | h[255 & l]) ^ c[n++]; a[b] = q; a[b + 1] = s; a[b + 2] = t; a[b + 3] = m; } return function (vid, tm, appVer, guid, platform, url, userAgent, referer, navCodeName, navName, navPlatform) { if (typeof url === 'undefined') url = window.document.URL; if (typeof userAgent === 'undefined') userAgent = window.navigator.userAgent; if (typeof referer === 'undefined') referer = window.document.referrer; if (typeof navCodeName === 'undefined') navCodeName = window.navigator.appCodeName; if (typeof navName === 'undefined') navName = window.navigator.appName; if (typeof navPlatform === 'undefined') navPlatform = window.navigator.platform; const textParts = ['', vid, tm, 'mg3c3b04ba', appVer, guid, platform, shorten(url), shorten(userAgent.toLowerCase()), shorten(referer), navCodeName, navName, navPlatform, '00', '']; textParts.splice(1, 0, calculateHash(textParts.join('|'))); const text = padText(textParts.join('|')); const [arr, len] = encodeText(text); encrypt(arr); return decodeText(arr, len).toUpperCase(); }; }(); /* ------------------------------------------------------------------ * * 状态与去重 * ------------------------------------------------------------------ */ var videoUrls = []; // {url, label} var subtitleUrls = []; // {url, label} var currentTitle = ''; function pushUrl(list, url, label) { if (!url) return; url = url.trim(); if (!/^https?:\/\//i.test(url)) return; for (var i = 0; i < list.length; i++) { if (list[i].url === url) return; } list.push({ url: url, label: label || '' }); render(); } function captureVideo(url, label) { pushUrl(videoUrls, url, label); } function captureSubtitle(url, label) { pushUrl(subtitleUrls, url, label); } /* ------------------------------------------------------------------ * * 从任意文本/JSON 中解析 m3u8 地址 * ------------------------------------------------------------------ */ function scanBody(txt, src) { if (!txt || typeof txt !== 'string') return; // 1) Tencent vinfo JSONP: QZOutputJson=...; var m = txt.match(/QZOutputJson\s*=\s*/); if (m) { try { var jsonStr = txt.slice(m.index + m[0].length).replace(/;\s*$/, ''); parseVinfo(JSON.parse(jsonStr)); return; } catch (e) { /* fallthrough to regex */ } } // 2) 尝试普通 JSON 解析 (部分接口直接返回 JSON) if (/^\s*[[{]/.test(txt)) { try { parseVinfo(JSON.parse(txt)); return; } catch (e) {} } // 3) 通用正则提取所有 .m3u8 var re = /https?:\/\/[^\s"'<>\\]+\.m3u8(?:\?[^\\s"'<>]*)?/gi; var mm; while ((mm = re.exec(txt))) { var u = mm[0]; if (/\.vtt\.m3u8/i.test(u)) captureSubtitle(u); else captureVideo(u); } } function parseVinfo(obj) { if (!obj) return; var vi = obj.vl && obj.vl.vi && obj.vl.vi[0]; if (!vi) return; if (vi.ti) setTitle(vi.ti); // 全部清晰度 (format list) var fl = (vi.fl && vi.fl.fi) || []; fl.forEach(function (f) { if (f.url) captureVideo(f.url, f.name || ('fmt-' + f.id)); }); // 当前选中清晰度 (url list) var ui = (vi.ul && vi.ul.ui) || []; ui.forEach(function (u) { if (u.url) captureVideo(u.url, 'current'); }); // 字幕轨 var sfl = (obj.sfl && obj.sfl.fi) || []; sfl.forEach(function (f) { if (f.url) captureSubtitle(f.url, f.name || f.lang); }); } function setTitle(t) { if (t) { currentTitle = t; var el = document.getElementById('wetv-parse-title'); if (el) el.textContent = t; } } /* ------------------------------------------------------------------ * * 网络请求钩子:捕获真实播放地址 * ------------------------------------------------------------------ */ // fetch 钩子 var origFetch = window.fetch; window.fetch = function (input, init) { var url = typeof input === 'string' ? input : (input && input.url) || ''; return origFetch.apply(this, arguments).then(function (response) { try { var u = response.url || url; if (/\.m3u8/i.test(u)) { if (/\.vtt\.m3u8/i.test(u)) captureSubtitle(u); else captureVideo(u); } var ct = (response.headers && response.headers.get && response.headers.get('content-type')) || ''; if (/json|javascript|text\/plain|xml/i.test(ct) && !/\.ts(\?|$)/i.test(u) && !/\.m3u8/i.test(u)) { response.clone().text().then(function (txt) { scanBody(txt, u); }).catch(function () {}); } } catch (e) {} return response; }); }; // XHR 钩子 var origOpen = XMLHttpRequest.prototype.open; var origSend = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.open = function (method, url) { this.__wtv_url = (typeof url === 'string') ? url : ''; return origOpen.apply(this, arguments); }; XMLHttpRequest.prototype.send = function () { var self = this; this.addEventListener('load', function () { try { var u = self.__wtv_url || self.responseURL || ''; if (/\.m3u8/i.test(u)) { if (/\.vtt\.m3u8/i.test(u)) captureSubtitle(u); else captureVideo(u); } var txt = self.responseText; if (txt && typeof txt === 'string' && txt.length < 500000) scanBody(txt, u); } catch (e) {} }); return origSend.apply(this, arguments); }; /* ------------------------------------------------------------------ * * getvinfo 接口:一次性列出全部清晰度(无需播放) * ------------------------------------------------------------------ */ function randHex(n) { var s = ''; for (var i = 0; i < n; i++) s += '0123456789abcdef'[Math.floor(Math.random() * 16)]; return s; } function extractVid() { var pm = location.pathname.match(/\/(?:play|playDetail)\/([^/]+)\/([^/?#]+)/); if (pm && pm[2]) return pm[2]; var q = new URLSearchParams(location.search); if (q.get('vid')) return q.get('vid'); return ''; } function apiResolve() { var vid = extractVid(); if (!vid) { toast('未能从当前页面提取 vid,请直接在播放页使用,或先点击播放'); return; } var guid = '6eb81823f6e496f9a87c88fbe977dee0'; var platform = '4830201'; var appVer = '3.5.57'; var tm = Math.floor(Date.now() / 1000); var flowid = randHex(32) + '_' + platform; var ckey = makeCKey(vid, tm, appVer, guid, platform, location.href); var p = { charge: 0, defaultfmt: 'auto', otype: 'json', guid: guid, flowid: flowid, platform: platform, sdtfrom: 1002, defnpayver: 0, appVer: appVer, sphttps: 1, tm: tm, spwm: 4, vid: vid, defn: '', fhdswitch: 0, show1080p: 0, isHLS: 1, dtype: 3, sphls: 2, spgzip: 1, dlver: 2, drm: 32, spau: 1, spaudio: 15, spsrt: 1, spvideo: 16, lang_code: 1491963, country_code: 153505, defsrc: 1, fp2p: 1, spadseg: 1, encryptVer: '8.1', cKey: ckey }; var qs = Object.keys(p).map(function (k) { return encodeURIComponent(k) + '=' + encodeURIComponent(p[k]); }).join('&'); var url = 'https://play.wetv.vip/getvinfo?' + qs; toast('正在请求 getvinfo ...'); GM_xmlhttpRequest({ method: 'GET', url: url, headers: { 'Referer': location.href, 'User-Agent': navigator.userAgent }, onload: function (r) { if (r.status >= 200 && r.status < 400) { scanBody(r.responseText, url); toast('getvinfo 解析完成'); } else { toast('getvinfo 请求失败 HTTP ' + r.status + '(可改点播放由实时捕获兜底)'); } }, onerror: function () { toast('getvinfo 网络错误(可点播放由实时捕获兜底)'); } }); } /* ------------------------------------------------------------------ * * UI 面板 * ------------------------------------------------------------------ */ GM_addStyle(` #wetv-parse-panel{position:fixed;top:16px;right:16px;z-index:999999;width:380px;max-height:80vh; background:rgba(18,18,22,.96);color:#e8e8ea;border:1px solid #2c2c34;border-radius:12px; font:13px/1.45 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Microsoft YaHei",sans-serif; box-shadow:0 12px 40px rgba(0,0,0,.5);display:flex;flex-direction:column;overflow:hidden;backdrop-filter:blur(6px);} #wetv-parse-panel.min #wetv-parse-body{display:none;} #wetv-parse-head{display:flex;align-items:center;gap:8px;padding:10px 12px;background:linear-gradient(90deg,#0083f5,#58caff);color:#fff;cursor:move;} #wetv-parse-head b{flex:1;font-size:13px;font-weight:600;} #wetv-parse-head .x{cursor:pointer;opacity:.85;font-weight:700;padding:0 4px;} #wetv-parse-head .x:hover{opacity:1;} #wetv-parse-body{padding:10px 12px;overflow:auto;} #wetv-parse-title{color:#9ad8ff;font-size:12px;margin-bottom:8px;word-break:break-all;} .wetv-sec{margin-bottom:10px;} .wetv-sec h4{margin:0 0 6px;font-size:12px;color:#ffb84d;display:flex;justify-content:space-between;} .wetv-sec h4 .c{color:#7fd1a0;} .wetv-row{display:flex;align-items:center;gap:6px;margin-bottom:5px;} .wetv-row .lbl{flex:0 0 auto;font-size:11px;color:#7fd1a0;max-width:90px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;} .wetv-row .u{flex:1;font-size:11px;color:#cfcfd6;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;} .wetv-row .b{flex:0 0 auto;background:#2c2c34;color:#fff;border:none;border-radius:6px;padding:3px 8px;cursor:pointer;font-size:11px;} .wetv-row .b:hover{background:#3a3a44;} .wetv-btns{display:flex;gap:8px;margin:4px 0 10px;} .wetv-btns button{flex:1;background:#0083f5;color:#fff;border:none;border-radius:8px;padding:8px;cursor:pointer;font-size:12px;font-weight:600;} .wetv-btns button.alt{background:#2c2c34;} .wetv-btns button:hover{filter:brightness(1.1);} .wetv-empty{color:#888;font-size:12px;padding:6px 0;} #wetv-parse-launch{position:fixed;bottom:24px;right:24px;z-index:999999; background:linear-gradient(90deg,#0083f5,#58caff);color:#fff;border:none;border-radius:24px; padding:11px 18px;cursor:pointer;font:600 13px/1 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Microsoft YaHei",sans-serif; box-shadow:0 8px 24px rgba(0,131,245,.45);display:flex;align-items:center;gap:8px;user-select:none;} #wetv-parse-launch:hover{filter:brightness(1.08);} #wetv-parse-launch .dot{width:8px;height:8px;border-radius:50%;background:#7fffa0;box-shadow:0 0 6px #7fffa0;} #wetv-parse-launch .cnt{display:inline-block;min-width:18px;text-align:center;background:rgba(0,0,0,.25);border-radius:10px;padding:1px 6px;font-size:11px;} #wetv-parse-panel{display:none;} #wetv-parse-toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%);z-index:999999; background:rgba(0,0,0,.85);color:#fff;padding:8px 16px;border-radius:20px;font-size:12px;opacity:0;transition:opacity .25s;pointer-events:none;} #wetv-parse-toast.show{opacity:1;} `); function buildPanel() { if (document.getElementById('wetv-parse-panel')) return; var panel = document.createElement('div'); panel.id = 'wetv-parse-panel'; panel.innerHTML = '