您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
sugar糖心解析
// ==UserScript== // @name sugar // @namespace http://tampermonkey.net/ // @version 1.0 // @description sugar糖心解析 // @author lidiaoo // @match *://txh*.com/* // @include *://txh*.com/* // @connect * // @grant GM_xmlhttpRequest // @grant GM_openInTab // @grant GM_getValue // @grant GM_setValue // @grant GM_log // @run-at document-start // @require https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.2.0/crypto-js.js // ==/UserScript== (function () { 'use strict'; // ==================== Crypto-JS加载检测 ==================== if (typeof window.CryptoJS === 'undefined') { alert('Crypto-JS加载失败,脚本无法运行!请检查网络或重新安装脚本。'); GM_log('[ERROR]', '[油猴1.0][错误] Crypto-JS未加载:检查bootcdn访问或脚本猫外部资源权限'); return; } const CryptoJS = window.CryptoJS; // ==================== GM_xmlhttpRequest可用性检测(脚本猫兼容) ==================== if (typeof GM_xmlhttpRequest === 'undefined') { alert('GM_xmlhttpRequest未启用!请按提示配置脚本猫权限:\n1. 打开脚本猫→当前脚本→脚本设置\n2. 权限管理→勾选"跨域请求权限"\n3. 保存后刷新页面'); GM_log('[ERROR]', '[油猴1.0][严重错误] GM_xmlhttpRequest不可用!脚本猫配置步骤:'); GM_log('[ERROR]', '1. 点击浏览器右上角脚本猫图标→进入"我的脚本"'); GM_log('[ERROR]', '2. 找到当前脚本→点击右侧"更多"→"脚本设置"'); GM_log('[ERROR]', '3. 进入"权限管理"→勾选"跨域请求权限"和"GM_xmlhttpRequest权限"'); GM_log('[ERROR]', '4. 关闭所有txh066.com标签页,重新打开'); return; } // 替换GM_log:用console.log,脚本猫日志面板可查看 GM_log('[INFO]', '[油猴1.0][成功] GM_xmlhttpRequest已启用,将用于解决CORS跨域'); // ==================== 唯一的CONFIG配置 ==================== const CONFIG = { aes: { key: 'fd14f9f8e38808fa', }, playLinkApi: { url: 'https://quantumultx.me', timeout: 2000, maxAttempts: 30, retryDelay: 1000, }, player: { onlinePlayer: 'https://m3u8player.org/player.html?url=', vlcProtocol: 'vlc://' }, targetApis: [ { match: '/h5/system/info', handler: handleSystemInfoApi }, { match: '/h5/movie/block', handler: handleMovieBlockApi }, { match: '/h5/user/info', handler: handleUserInfoApi }, { match: '/h5/movie/detail', handler: handleMovieDetailApi }, { match: '/h5/movie/search', handler: handleMovieSearchApi }, { match: '/h5/danmaku/list', handler: handleDanmakuApi } ], script: { targetReg: /\/_nuxt\/[\w]+\.js$/, jumpCode: 'e&&(window.location.href="https://www.baidu.com")', marker: 'data-userscript-handled' } }; // ==================== 工具函数:将CONFIG转换为注入脚本可用的字符串 ==================== function getConfigString() { // 处理targetApis中的handler(保持函数名字符串) const targetApisStr = CONFIG.targetApis.map(api => `{ match: '${api.match}', handler: ${api.handler} }` ).join(','); // 拼接完整CONFIG字符串(确保CryptoJS相关属性正确引用) return `const CONFIG = { aes: { key: '${CONFIG.aes.key}', }, playLinkApi: { url: '${CONFIG.playLinkApi.url}', maxAttempts: ${CONFIG.playLinkApi.maxAttempts}, retryDelay: ${CONFIG.playLinkApi.retryDelay}, }, player: { onlinePlayer: '${CONFIG.player.onlinePlayer}', vlcProtocol: '${CONFIG.player.vlcProtocol}' }, targetApis: [ { match: '/h5/system/info', handler: handleSystemInfoApi }, { match: '/h5/movie/block', handler: handleMovieBlockApi }, { match: '/h5/user/info', handler: handleUserInfoApi }, { match: '/h5/movie/detail', handler: handleMovieDetailApi }, { match: '/h5/movie/search', handler: handleMovieSearchApi }, { match: '/h5/danmaku/list', handler: handleDanmakuApi } ], };`; } if (CONFIG.playLinkApi.url === 'undefined' || CONFIG.playLinkApi.url === '') { alert('请先填入解析地址'); GM_log('[ERROR]', '[油猴1.0][错误] 请先填入解析地址'); return; } // ==================== 禁止调试逻辑 ==================== GM_log('[INFO]', '[油猴1.0] 脚本启动==============================================='); // 禁用右键 document.addEventListener('contextmenu', e => { e.preventDefault(); GM_log('[INFO]', '[油猴1.0][禁止调试] 右键菜单已禁用'); }); // 禁用F12/快捷键 document.addEventListener('keydown', e => { if (e.key === 'F12' || (e.ctrlKey && e.shiftKey && (e.key === 'I' || e.key === 'J')) || (e.ctrlKey && e.key === 'U')) { e.preventDefault(); GM_log('[INFO]', '[油猴1.0][禁止调试] 开发者工具快捷键已禁用'); } }); // 拦截debugger window.debugger = () => GM_log('[INFO]', '[油猴1.0][禁止调试] debugger语句已拦截'); // 处理noscript const handleNoscript = () => { document.querySelectorAll('noscript').forEach(tag => tag.style.display = 'none'); GM_log('[INFO]', '[油猴1.0][处理noscript] 已隐藏noscript标签'); }; document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', handleNoscript) : handleNoscript(); // ==================== AES工具函数 ==================== function aesEcbDecrypt(cipherText) { try { const cipherParams = CryptoJS.lib.CipherParams.create({ ciphertext: CryptoJS.enc.Base64.parse(cipherText) }); const decrypted = CryptoJS.AES.decrypt(cipherParams, CryptoJS.enc.Utf8.parse(CONFIG.aes.key), { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 }); return decrypted.toString(CryptoJS.enc.Utf8); } catch (e) { GM_log('[ERROR]', `[油猴1.0][AES解密失败] ${e.message}`); throw e; } } function aesEcbEncrypt(plainText) { try { const paddedText = CryptoJS.enc.Utf8.parse(plainText); const encrypted = CryptoJS.AES.encrypt(paddedText, CryptoJS.enc.Utf8.parse(CONFIG.aes.key), { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 }); return encrypted.toString(); } catch (e) { GM_log('[ERROR]', `[油猴1.0][AES加密失败] ${e.message}`); throw e; } } // ==================== 播放链接获取(脚本猫GM兼容) ==================== async function getPlayLink(videoId) { GM_log('[INFO]', `[油猴1.0][播放链接] 开始获取(videoId:${videoId}),最多重试${CONFIG.playLinkApi.maxAttempts}次`); for (let attempt = 1; attempt <= CONFIG.playLinkApi.maxAttempts; attempt++) { try { let responseText = await new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'POST', url: CONFIG.playLinkApi.url, anonymous: true, headers: { 'Content-Type': 'application/json' }, data: JSON.stringify({video_id: videoId}), timeout: CONFIG.playLinkApi.timeout, onload: (res) => { GM_log('[INFO]', `[油猴1.0][播放链接] 第${attempt}次请求状态:${res.status}`); if (res.status === 200) { resolve(res.responseText); } else { reject(new Error(`服务器返回非200状态:${res.status}(响应:${res.responseText.slice(0, 100)})`)); } }, onerror: (err) => { reject(new Error(`GM请求错误:${err.message}(可能是网络问题或服务器拒绝)`)); }, ontimeout: () => { reject(new Error(`GM请求超时(${CONFIG.playLinkApi.url})`)); } }); }); // 解析JSON let result; try { result = JSON.parse(responseText); } catch (parseErr) { throw new Error(`响应JSON解析失败:${parseErr.message}(原始响应:${responseText.slice(0, 100)})`); } // 校验播放链接 if (result?.playLink && typeof result.playLink === 'string' && result.playLink.startsWith('http')) { GM_log('[ERROR]', `[油猴1.0][播放链接] 第${attempt}次尝试成功:${result.playLink}`); openPlayers(result.playLink); // alert(`获取播放链接成功 ${result.playLink}`); return result.playLink; } else { throw new Error(`无有效playLink(响应:${JSON.stringify(result)})`); } } catch (e) { GM_log('[ERROR]', `[油猴1.0][播放链接] 第${attempt}次尝试失败:${e.message}`); if (attempt < CONFIG.playLinkApi.maxAttempts) { await new Promise(resolve => setTimeout(resolve, CONFIG.playLinkApi.retryDelay)); } } } GM_log('[ERROR]', `[油猴1.0][播放链接] 已尝试${CONFIG.playLinkApi.maxAttempts}次,全部失败`); alert(`获取播放链接失败!请检查:1. 网络是否能访问 ${CONFIG.playLinkApi.url} 2. 刷新页面重试`); return ""; } // ==================== 播放器唤起 ==================== function openPlayers(playLink) { if (!playLink) return; try { const onlinePlayerUrl = CONFIG.player.onlinePlayer + encodeURIComponent(playLink); // 可以添加更多参数,如是否激活新标签页 GM_openInTab(onlinePlayerUrl, {active: true}); GM_log('[INFO]', `[油猴1.0][浏览器播放] 已打开浏览器播放:${onlinePlayerUrl}`); } catch (e) { GM_log('[ERROR]', `[油猴1.0][浏览器播放] 唤起浏览器播放失败:${e.message}`); } // try { // const vlcUrl = CONFIG.player.vlcProtocol + encodeURIComponent(playLink); // // 可以添加更多参数,如是否激活新标签页 // GM_openInTab(vlcUrl, {active: false}); // GM_log('[INFO]',`[油猴1.0][播放器] 尝试唤起VLC:${vlcUrl}(需提前关联vlc://协议)`); // } catch (e) { // GM_log('[ERROR]',`[油猴1.0][播放器] 唤起VLC失败:${e.message}`); // } } // ==================== API数据处理 ==================== function handleUserInfoApi(decryptedStr) { try { GM_log('[INFO]', '[油猴1.0][用户信息API] 开始处理'); let userData = JSON.parse(decryptedStr); userData.data.is_vip = 'y'; userData.data.is_dark_vip = 'y'; userData.data.exp_level = 6; userData.data.balance = '99999999'; userData.data.balance_income = '99999999'; userData.data.balance_freeze = '0'; userData.data.group_end_time = '2999-09-09到期'; userData.data.post_banner = []; userData.data.bottom_ads = []; userData.data.bottom_ad = {}; userData.data.layer_ad = {}; userData.data.layer_ads = []; userData.data.layer_app = []; userData.data.ad = {}; userData.data.ads = []; userData.data.notice = ''; userData.data.ad_auto_jump = 'n'; userData.data.site_url = ''; userData.data.dark_tips = ''; //const playLink = getPlayLink('33545'); return JSON.stringify(userData); } catch (e) { GM_log('[ERROR]', `[油猴1.0][用户信息API处理失败] ${e.message}`); return decryptedStr; } } function handleSystemInfoApi(decryptedStr) { try { GM_log('[INFO]', '[油猴1.0][系统信息API] 开始处理'); let systemData = JSON.parse(decryptedStr); systemData.data.post_banner = []; systemData.data.bottom_ads = []; systemData.data.bottom_ad = {}; systemData.data.layer_ad = {}; systemData.data.layer_ads = []; systemData.data.layer_app = []; systemData.data.ad = {}; systemData.data.ads = []; systemData.data.notice = ''; systemData.data.ad_auto_jump = 'y'; systemData.data.ad_show_time = 0; systemData.data.site_url = ''; systemData.data.dark_tips = ''; return JSON.stringify(systemData); } catch (e) { GM_log('[ERROR]', `[油猴1.0][系统信息API处理失败] ${e.message}`); return decryptedStr; } } function handleMovieBlockApi(decryptedStr) { try { GM_log('[INFO]', '[油猴1.0][系统视频信息API] 开始处理'); let movieData = JSON.parse(decryptedStr); movieData = { ...movieData, data: movieData.data.map(item => ({ ...item, ad: [] // 设为空数组 })) }; return JSON.stringify(movieData); } catch (e) { GM_log('[ERROR]', `[油猴1.0][系统视频信息API处理失败] ${e.message}`); return decryptedStr; } } function handleMovieDetailApi(decryptedStr) { try { GM_log('[INFO]', '[油猴1.0][电影详情API] 开始处理'); let movieData = JSON.parse(decryptedStr); movieData.data.balance = '99999999'; movieData.data.balance_income = '99999999'; movieData.data.balance_freeze = '0'; if (movieData?.data?.lines && movieData.data.lines.length >= 2) { const vipLine = movieData.data.lines[1]; if (vipLine?.link) { movieData.data.backup_link = vipLine.link; movieData.data.play_link = vipLine.link; GM_log('[INFO]', `[油猴1.0][电影详情API] 播放线路:切换为VIP线路 → ${vipLine.link.slice(0, 50)}...`); } } movieData.data.name = '[油猴]___视频id: ' + movieData.data.id + ' 名称: ' + movieData.data.name; movieData.data.exp_level = 0; movieData.data.ad = []; movieData.data.ads = []; movieData.data.ad_apps = []; movieData.data.has_buy = 'y'; movieData.data.has_favorite = 'y'; movieData.data.has_follow = 'y'; movieData.data.has_love = 'y'; movieData.data.pay_type = 'y'; movieData.data.money = 0; movieData.data.play_ads = []; movieData.data.play_ad_auto_jump = 'y'; movieData.data.play_ad_show_time = 0; const videoId = movieData?.data?.id; let origin_play_link = movieData.data.play_link; movieData.data.play_link = ''; movieData.data.backup_link = ''; // if (videoId) { // const playLink = getPlayLink(videoId); // if (playLink&&typeof playLink!=="undefined"&&playLink!=="") { // const startStr = "/h5/m3u8/link"; // const startIndex = playLink.indexOf(startStr); // if (startIndex !== -1) { // // 从startStr开始截取到结尾(包含后续所有内容) // const subLink = playLink.slice(startIndex); // GM_log('[INFO]',subLink); // // 输出: /h5/m3u8/link/567c5935bd1de50452f0d601a6ef634b.m3u8 // movieData.data.backup_link = subLink; // movieData.data.play_link = subLink; // } // }else{ // movieData.data.play_link = origin_play_link; // movieData.data.backup_link = origin_play_link; // } // } return JSON.stringify(movieData); } catch (e) { GM_log('[ERROR]', `[油猴1.0][电影详情API处理失败] ${e.message}`); return decryptedStr; } } function handleMovieSearchApi(decryptedStr) { try { GM_log('[INFO]', '[油猴1.0][电影搜索API] 开始处理'); let movieData = JSON.parse(decryptedStr); movieData.data = movieData.data.filter(item => item.type != 'ad'); return JSON.stringify(movieData); } catch (e) { GM_log('[ERROR]', `[油猴1.0][电影搜索API处理失败] ${e.message}`); return decryptedStr; } } function handleDanmakuApi(decryptedStr) { try { GM_log('[INFO]', '[油猴1.0][电影danmakuAPI] 开始处理'); let movieData = JSON.parse(decryptedStr); // movieData.data = []; return JSON.stringify(movieData); } catch (e) { GM_log('[ERROR]', `[油猴1.0][电影danmakuAPI处理失败] ${e.message}`); return decryptedStr; } } // ==================== API路由 ==================== function routeApiHandler(requestUrl, originData) { let match = false for (const api of CONFIG.targetApis) { if (requestUrl.includes(api.match)) { try { // 核心流程:解密→业务处理→加密 match = true const decrypted = aesEcbDecrypt(originData); GM_log('[INFO]', `[油猴1.0][API路由] 匹配成功:${api.match} → 执行${api.handler.name}`); let resDataStr = api.handler(decrypted) let res = aesEcbEncrypt(resDataStr); const testDecrypted = aesEcbDecrypt(res); return res } catch (e) { GM_log('[ERROR]', `[油猴1.0][目标API处理出错:] ${e.message}`); return originData; } } } if (!match) { GM_log('[INFO]', `[油猴1.0][API路由] 未匹配目标API:${requestUrl}`); return originData; } } // ==================== XHR拦截 ==================== GM_log('[INFO]', '[油猴][XHR拦截] 启用新拦截方式(原型链重写)'); const XHR = XMLHttpRequest; const nativeOpen = XHR.prototype.open; const nativeSend = XHR.prototype.send; XHR.prototype.open = function (method, url, ...args) { this._url = url; return nativeOpen.apply(this, [method, url, ...args]); }; XHR.prototype.send = function (body) { const xhr = this; const userLoad = xhr.onload; const userReady = xhr.onreadystatechange; xhr.onload = null; xhr.onreadystatechange = null; xhr.addEventListener('readystatechange', async () => { if (xhr.readyState !== 4) return; try { const raw = (xhr.responseType === '' || xhr.responseType === 'text') ? xhr.responseText : xhr.response; const modified = routeApiHandler(xhr._url, raw); //await new Promise() Object.defineProperty(xhr, 'responseText', { value: modified, writable: false, configurable: true }); if (xhr.responseType === '' || xhr.responseType === 'text') { Object.defineProperty(xhr, 'response', { value: modified, writable: false, configurable: true }); } if (userReady) userReady.call(xhr); if (userLoad) userLoad.call(xhr); } catch (e) { GM_log('[ERROR]', '[XHR-rewrite] async error', e); if (userReady) userReady.call(xhr); if (userLoad) userLoad.call(xhr); } }); nativeSend.call(xhr, body); }; /* ************** 注入工具 ************** */ // 监听注入JS发送的"状态查询/更新"事件 unsafeWindow.addEventListener('gmStateOperation', function (event) { const {type, data, callbackId} = event.detail; // 处理不同类型的操作 switch (type) { // 查询当前活跃请求ID case 'query': const activeId = GM_getValue('activeRequestId', ''); sendResponse(callbackId, {success: true, data: activeId}); break; // 更新当前活跃请求ID(新请求覆盖旧请求) case 'update': GM_setValue('activeRequestId', data); sendResponse(callbackId, {success: true}); break; // 清除活跃请求ID(请求完成) case 'clear': GM_setValue('activeRequestId', ''); sendResponse(callbackId, {success: true}); break; } }); // 向注入JS发送响应(通过事件) function sendResponse(callbackId, response) { unsafeWindow.dispatchEvent(new CustomEvent('gmStateResponse', { detail: { callbackId: callbackId, response: response } })); } /* 0. 先给页面埋一个 script 标签,把抢 XHR 的代码塞进去 */ const inject = (code) => { try { // 直接创建脚本标签,不等待DOM加载事件 const s = document.createElement('script'); s.textContent = code; // 优先插入head,若head未就绪则插入documentElement const target = document.head || document.documentElement; if (target) { target.appendChild(s); console.log('脚本注入成功(在fetch前)'); return true; } else { console.error('注入失败:未找到合适的父节点'); alert('注入失败:未找到合适的父节点') return false; } } catch (error) { console.error('注入失败:', error); alert('注入失败:', error) return false; } }; /* ************** 油猴沙箱端:监听+代发请求 ************** */ window.addEventListener('TM_fetchPlayLink', function (e) { const {videoId, ticket} = e.detail; console.error('[TM][GM] 收到页面请求,videoId=' + videoId + ', ticket=' + ticket); GM_xmlhttpRequest({ method: 'POST', url: CONFIG.playLinkApi.url, anonymous: true, headers: { 'Content-Type': 'application/json' }, data: JSON.stringify({video_id: videoId}), timeout: CONFIG.playLinkApi.timeout, onload: (res) => { GM_log('[ERROR]', '[TM][GM] videoId' + videoId + ' 请求成功,状态=' + res.status + ', 长度=' + res.responseText.length + '响应' + res.responseText); window.dispatchEvent(new CustomEvent('TM_playLinkResult', { detail: {ticket: ticket, ok: true, text: res.responseText} })); }, onerror: function (err) { GM_log('[ERROR]', '[TM][GM] videoId' + videoId + ' 请求失败', err); window.dispatchEvent(new CustomEvent('TM_playLinkResult', { detail: {ticket: ticket, ok: false} })); }, ontimeout: function () { GM_log('[ERROR]', '[TM][GM] videoId' + videoId + ' 请求超时'); window.dispatchEvent(new CustomEvent('TM_playLinkResult', { detail: {ticket: ticket, ok: false} })); } }); }); /* ************** 先把 CryptoJS 插进去 ************** */ const cryptoScript = document.createElement('script'); cryptoScript.src = 'https://cdn.bootcdn.net/ajax/libs/crypto-js/4.2.0/crypto-js.js'; cryptoScript.onload = () => { /* ************** 1. 所有主逻辑(CONFIG + 工具函数 + XHR 代理) ************** */ const mainLogic = ` debugger /* 复用主脚本的CONFIG配置 */ ${getConfigString()} /* ---------------- AES 工具 ---------------- */ function aesEcbDecrypt(cipherText){ try{ const cipherParams = CryptoJS.lib.CipherParams.create({ ciphertext: CryptoJS.enc.Base64.parse(cipherText) }); const decrypted = CryptoJS.AES.decrypt(cipherParams, CryptoJS.enc.Utf8.parse(CONFIG.aes.key), { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 }); return decrypted.toString(CryptoJS.enc.Utf8); }catch(e){ console.error('[TM][AES解密失败]', e); throw e; } } function aesEcbEncrypt(plainText){ try{ const encrypted = CryptoJS.AES.encrypt(plainText, CryptoJS.enc.Utf8.parse(CONFIG.aes.key), { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 }); return encrypted.toString(); }catch(e){ console.error('[TM][AES加密失败]', e); throw e; } } // ==================== 6. 播放链接获取(脚本猫GM兼容) ==================== /* 2.1 工具:发消息给油猴并等待结果 */ function askTampermonkey(videoId){ return new Promise(function(resolve,reject){ const ticket = Math.random().toString(36).slice(2); function listen(e){ if(e.detail.ticket === ticket){ window.removeEventListener('TM_playLinkResult', listen); e.detail.ok ? resolve(e.detail.text) : reject(new Error('GM 请求失败')); } } window.addEventListener('TM_playLinkResult', listen); window.dispatchEvent(new CustomEvent('TM_fetchPlayLink', {detail:{videoId:videoId, ticket:ticket}})); }); } /* 2.2 你的 getPlayLink:只负责调度,不真正发请求 */ // 与Tampermonkey通信的工具函数(封装事件调用) const gmState = { // 发送状态操作请求(返回Promise) request: function(type, data) { return new Promise((resolve) => { const callbackId = 'cb_' + Date.now() + Math.random().toString(36).slice(2); // 监听响应事件 const handleResponse = function(event) { if (event.detail.callbackId === callbackId) { window.removeEventListener('gmStateResponse', handleResponse); resolve(event.detail.response); } }; window.addEventListener('gmStateResponse', handleResponse); // 发送操作请求 window.dispatchEvent(new CustomEvent('gmStateOperation', { detail: { type, data, callbackId } })); }); }, // 查询当前活跃请求ID queryActiveId: function() { return this.request('query'); }, // 更新活跃请求ID updateActiveId: function(id) { return this.request('update', id); }, // 清除活跃请求ID clearActiveId: function() { return this.request('clear'); } }; // /** // * 获取播放链接(注入JS核心函数) // * @param {string} videoId - 视频ID // * @returns {Promise<string|null>} // */ // async function getPlayLinkAsync(videoId) { // // 生成当前请求唯一标识 // const requestId = 'req_' + Date.now() + Math.random().toString(36).slice(2); // console.log('[Injected] 发起新请求,videoId=' + videoId + ',requestId=' + requestId); // // 通知外部更新活跃请求ID(覆盖旧请求) // await gmState.updateActiveId(requestId); // try { // // 重试循环 // for (let attempt = 1; attempt <= CONFIG.playLinkApi.maxAttempts; attempt++) { // // 检查当前活跃ID是否为自己(新请求会覆盖此值) // const currentActiveId = (await gmState.queryActiveId()).data; // if (currentActiveId !== requestId) { // console.error('[Injected] 检测到新请求,当前请求停止重试(videoId=' + videoId + ' 本requestId=' + requestId + ')'); // return ""; // } // try { // console.error('[Injected] 第' + attempt + '次尝试(videoId=' + videoId + ' 本requestId=' + requestId + ')'); // const responseText = await askTampermonkey(videoId); // const responseJson = JSON.parse(responseText); // if (responseJson && responseJson.playLink && responseJson.playLink.indexOf('http') === 0) { // //debugger // console.error('[Injected] videoId=' + videoId + ' 成功获取播放链接:' + responseJson.playLink); // //openPlayers(responseJson.playLink); // // await gmState.clearActiveId(); // 清除状态 // return responseJson.playLink; // } // } catch (error) { // console.error('[Injected] videoId=' + videoId + ' 第' + attempt + '次尝试失败:' + error.message); // } // // 重试前等待 // await new Promise(resolve => setTimeout(resolve, CONFIG.playLinkApi.retryDelay)); // // 等待后再次检查活跃状态 // const activeAfterWait = (await gmState.queryActiveId()).data; // if (activeAfterWait !== requestId) { // console.error('[Injected] videoId=' + videoId + ' 等待后检测到新请求,停止重试(requestId=' + requestId + ')'); // return ""; // } // } // // 所有重试失败 // console.error('[Injected] videoId=' + videoId + ' 达到最大重试次数(requestId=' + requestId + ')'); // await gmState.clearActiveId(); // return ""; // } catch (error) { // console.error('[Injected] videoId=' + videoId + ' 请求异常:' + error.message); // // 仅当自己仍为活跃请求时才清除状态 // const currentActiveId = (await gmState.queryActiveId()).data; // if (currentActiveId === requestId) { // await gmState.clearActiveId(); // } // return ""; // } // } /** * 获取播放链接(注入JS核心函数) * @param {string} videoId - 视频ID * @returns {Promise<string|null>} */ async function getPlayLink(videoId) { // 生成当前请求唯一标识 const requestId = 'req_' + Date.now() + Math.random().toString(36).slice(2); console.log('[Injected] 发起新请求,videoId=' + videoId + ',requestId=' + requestId); // 通知外部更新活跃请求ID(覆盖旧请求) // await gmState.updateActiveId(requestId); try { // 重试循环 for (let attempt = 1; attempt <= CONFIG.playLinkApi.maxAttempts; attempt++) { // 检查当前活跃ID是否为自己(新请求会覆盖此值) const currentActiveId = (await gmState.queryActiveId()).data; // if (currentActiveId !== requestId) { // console.error('[Injected] 检测到新请求,当前请求停止重试(videoId=' + videoId + ' 本requestId=' + requestId + ')'); // return ""; // } try { console.error('[Injected] 第' + attempt + '次尝试(videoId=' + videoId + ' 本requestId=' + requestId + ')'); const responseText = await askTampermonkey(videoId); const responseJson = JSON.parse(responseText); if (responseJson && responseJson.playLink && responseJson.playLink.indexOf('http') === 0) { //debugger console.error('[Injected] videoId=' + videoId + ' 成功获取播放链接:' + responseJson.playLink); //openPlayers(responseJson.playLink); // await gmState.clearActiveId(); // 清除状态 return responseJson.playLink; } } catch (error) { console.error('[Injected] videoId=' + videoId + ' 第' + attempt + '次尝试失败:' + error.message); } // 重试前等待 await new Promise(resolve => setTimeout(resolve, CONFIG.playLinkApi.retryDelay)); // // 等待后再次检查活跃状态 // const activeAfterWait = (await gmState.queryActiveId()).data; // if (activeAfterWait !== requestId) { // console.error('[Injected] videoId=' + videoId + ' 等待后检测到新请求,停止重试(requestId=' + requestId + ')'); // return ""; // } } // 所有重试失败 console.error('[Injected] videoId=' + videoId + ' 达到最大重试次数(requestId=' + requestId + ')'); // await gmState.clearActiveId(); return ""; } catch (error) { console.error('[Injected] videoId=' + videoId + ' 请求异常:' + error.message); // 仅当自己仍为活跃请求时才清除状态 // const currentActiveId = (await gmState.queryActiveId()).data; // if (currentActiveId === requestId) { // await gmState.clearActiveId(); // } return ""; } } async function handleUserInfoApi(decryptedStr) { try { console.log('[油猴1.0][用户信息API] 开始处理'); let userData = JSON.parse(decryptedStr); userData.data.is_vip = 'y'; userData.data.is_dark_vip = 'y'; userData.data.exp_level = 6; userData.data.balance = '99999999'; userData.data.balance_income = '99999999'; userData.data.balance_freeze = '0'; userData.data.group_end_time = '2999-09-09到期'; userData.data.post_banner = []; userData.data.bottom_ads = []; userData.data.bottom_ad = {}; userData.data.layer_ad = {}; userData.data.layer_ads = []; userData.data.layer_app = []; userData.data.ad = {}; userData.data.ads = []; userData.data.notice = ''; userData.data.ad_auto_jump = 'n'; userData.data.site_url = ''; userData.data.dark_tips = ''; return JSON.stringify(userData); } catch (e) { console.error('[油猴1.0][用户信息API处理失败]',e.message); return decryptedStr; } } async function handleSystemInfoApi(decryptedStr) { try { console.log('[油猴1.0][系统信息API] 开始处理'); let systemData = JSON.parse(decryptedStr); systemData.data.post_banner = []; systemData.data.bottom_ads = []; systemData.data.bottom_ad = {}; systemData.data.layer_ad = {}; systemData.data.layer_ads = []; systemData.data.layer_app = []; systemData.data.ad = {}; systemData.data.ads = []; systemData.data.notice = ''; systemData.data.ad_auto_jump = 'y'; systemData.data.ad_show_time = 0; systemData.data.site_url = ''; systemData.data.dark_tips = ''; return JSON.stringify(systemData); } catch (e) { console.error('[油猴1.0][系统信息API处理失败]',e.message); return decryptedStr; } } async function handleMovieBlockApi(decryptedStr) { try { console.log('[油猴1.0][系统视频信息API] 开始处理'); let movieData = JSON.parse(decryptedStr); movieData = { ...movieData, data: movieData.data.map(item => ({ ...item, ad: [] // 设为空数组 })) }; return JSON.stringify(movieData); } catch (e) { console.error('[油猴1.0][系统视频信息API处理失败]',e.message); return decryptedStr; } } async function handleMovieDetailApi(decryptedStr) { try { console.log('[油猴1.0][电影详情API] 开始处理'); let movieData = JSON.parse(decryptedStr); movieData.data.balance = '99999999'; movieData.data.balance_income = '99999999'; movieData.data.balance_freeze = '0'; // if (movieData?.data?.lines && movieData.data.lines.length >= 2) { // const vipLine = movieData.data.lines[1]; // if (vipLine?.link) { // movieData.data.backup_link = vipLine.link; // movieData.data.play_link = vipLine.link; // console.log('[油猴1.0][电影详情API] 播放线路:切换为VIP线路 → ',vipLine.link.slice(0, 50)); // } // } movieData.data.name = '[油猴]___视频id: ' +movieData.data.id + ' 名称: ' + movieData.data.name; movieData.data.exp_level = 0; movieData.data.ad = []; movieData.data.ads = []; movieData.data.ad_apps = []; movieData.data.has_buy = 'y'; movieData.data.has_favorite = 'y'; movieData.data.has_follow = 'y'; movieData.data.has_love = 'y'; movieData.data.pay_type = 'y'; movieData.data.money = 0; movieData.data.play_ads = []; movieData.data.play_ad_auto_jump = 'y'; movieData.data.play_ad_show_time = 0; const videoId = movieData?.data?.id; let origin_play_link = movieData.data.play_link; movieData.data.play_link = ''; movieData.data.backup_link = ''; if (videoId) { const playLink = await getPlayLink(videoId); //const playLink = ''; if (playLink&&typeof playLink!=="undefined"&&playLink!=="") { const startStr = "/h5/m3u8/link"; const startIndex = playLink.indexOf(startStr); if (startIndex !== -1) { // 从startStr开始截取到结尾(包含后续所有内容) const subLink = playLink.slice(startIndex); console.log(subLink); // 输出: /h5/m3u8/link/567c5935bd1de50452f0d601a6ef634b.m3u8 movieData.data.backup_link = subLink; movieData.data.play_link = subLink; } }else{ movieData.data.play_link = origin_play_link; movieData.data.backup_link = origin_play_link; } } // movieData.data.backup_link = ''; // movieData.data.play_link = ''; return JSON.stringify(movieData); } catch (e) { console.error('[油猴1.0][电影详情API处理失败]',e.message); return decryptedStr; } } async function handleMovieSearchApi(decryptedStr) { try { console.log('[油猴1.0][电影搜索API] 开始处理'); let movieData = JSON.parse(decryptedStr); movieData.data = movieData.data.filter(item => item.type != 'ad'); return JSON.stringify(movieData); } catch (e) { console.error('[油猴1.0][电影搜索API处理失败]',e.message); return decryptedStr; } } async function handleDanmakuApi(decryptedStr) { try { console.log('[油猴1.0][电影danmakuAPI] 开始处理'); let movieData = JSON.parse(decryptedStr); // movieData.data = []; return JSON.stringify(movieData); } catch (e) { console.error('[油猴1.0][电影danmakuAPI处理失败]',e.message); return decryptedStr; } } // ==================== API路由 ==================== async function routeApiHandler(requestUrl, originData) { let match = false for (const api of CONFIG.targetApis) { if (requestUrl.includes(api.match)) { try { // 核心流程:解密→业务处理→加密 match = true const decrypted = aesEcbDecrypt(originData); console.log('[油猴1.0][API路由] 匹配成功',api.match,' → 执行',api.handler.name); let resDataStr = await api.handler(decrypted) let res = aesEcbEncrypt(resDataStr); const testDecrypted = aesEcbDecrypt(res); return res } catch (e) { console.error('[油猴1.0][目标API处理出错:]',e.message); return originData; } } } if (!match) { console.log('[油猴1.0][API路由] 未匹配目标API:',requestUrl); return originData; } } function routeApiHandlerAddUrl(requestUrl, originData) { let match = false for (const api of CONFIG.targetApis) { if (requestUrl.includes(api.match)) { try { // 核心流程:解密→业务处理→加密 match = true const decryptedStr = aesEcbDecrypt(originData); console.log('[油猴1.0][API路由] 匹配成功',api.match,' → 执行',api.handler.name); let resDataStr = decryptedStr; try { console.log('[油猴1.0][API_URL增加] 开始处理'); let movieData = JSON.parse(decryptedStr); movieData.api_url = requestUrl; resDataStr = JSON.stringify(movieData); } catch (e) { console.error('[油猴1.0][API_URL增加处理失败]',e.message); } let res = aesEcbEncrypt(resDataStr); const testDecrypted = aesEcbDecrypt(res); return res } catch (e) { console.error('[油猴1.0][目标API_URL增加处理出错:]',e.message); return originData; } } } if (!match) { console.log('[油猴1.0][API_URL增加] 未匹配目标API:',requestUrl); return originData; } } /* ---------------- XHR 代理 ---------------- */ const XHR = XMLHttpRequest; const nativeOpen = XHR.prototype.open; const nativeSend = XHR.prototype.send; XHR.prototype.open = function (method, url, ...args) { this._url = url; return nativeOpen.apply(this, [method, url, ...args]); }; XHR.prototype.send = function (body) { const xhr = this; const userLoad = xhr.onload; const userReady = xhr.onreadystatechange; xhr.onload = null; xhr.onreadystatechange = null; xhr.addEventListener('readystatechange', () => { if (xhr.readyState !== 4) return; try { const raw = (xhr.responseType === '' || xhr.responseType === 'text') ? xhr.responseText : xhr.response; const modified = routeApiHandlerAddUrl(xhr._url, raw); Object.defineProperty(xhr, 'responseText', { value: modified, writable: false, configurable: true }); if (xhr.responseType === '' || xhr.responseType === 'text') { Object.defineProperty(xhr, 'response', { value: modified, writable: false, configurable: true }); } if (userReady) userReady.call(xhr); if (userLoad) userLoad.call(xhr); } catch (e) { console.error('[XHR-rewrite] async error', e); if (userReady) userReady.call(xhr); if (userLoad) userLoad.call(xhr); } }); //debugger nativeSend.call(xhr, body); }; `; inject(mainLogic); }; cryptoScript.onerror = () => GM_log('[ERROR]', '[TM] CryptoJS 外部地址加载失败'); document.documentElement.insertBefore(cryptoScript, document.documentElement.firstChild); // ==================== Fetch拦截 ==================== const originalFetch = window.fetch; window.fetch = async function (input, init) { const request = input instanceof Request ? input : new Request(input, init); const requestUrl = request.url; const isTargetApi = CONFIG.targetApis.some(api => requestUrl.includes(api.match)); if (!isTargetApi) return originalFetch.apply(this, arguments); GM_log('[INFO]', `[油猴1.0][Fetch监听] 处理目标API:${requestUrl}`); try { const originalRes = await originalFetch.apply(this, arguments); const resClone = originalRes.clone(); const originData = await resClone.text(); const data = await routeApiHandler(requestUrl, originData); return new Response(data, { status: originalRes.status, statusText: originalRes.statusText, headers: originalRes.headers }); } catch (e) { GM_log('[ERROR]', `[油猴1.0][Fetch处理失败] API:${requestUrl} → ${e.message}`); alert(`[油猴1.0][Fetch处理失败] API:${requestUrl} → ${e.message}`) return originalFetch.apply(this, arguments); } }; // ==================== Script处理 ==================== async function handleScriptNode(originalScript) { const scriptUrl = originalScript.src; if (!scriptUrl || !CONFIG.script.targetReg.test(scriptUrl)) return; try { originalScript.parentNode?.removeChild(originalScript); GM_log('[INFO]', `[油猴1.0][Script处理] 移除原Nuxt脚本:${scriptUrl}`); const response = await fetch(scriptUrl); if (!response.ok) throw new Error(`脚本请求失败:${response.status}`); let scriptText = await response.text(); const oldLength = scriptText.length; scriptText = scriptText.replace(CONFIG.script.jumpCode, ''); if (scriptText.length != oldLength) { GM_log('[INFO]', `[油猴1.0][Script处理] 已移除跳转代码:${CONFIG.script.jumpCode}`); } // debugger GM_log('[INFO]', `[油猴1.0][Script处理] 已替换函数为异步函数`); const hasScriptTxt = scriptText.indexOf('请求体解析错误'); if (hasScriptTxt !== -1) { const oldLength = scriptText.length; // scriptText = scriptText.replaceAll('"request",(function', '"request",(async function') scriptText = scriptText.replaceAll('transformResponse:function(e){try{return JSON.parse(e)}catch(e){}var n;try{var t=yn.decrypt(e,jn,{mode:xn}).toString(wn);n=JSON.parse(t)}catch(e){n={status:"n",error:"数据解析错误"}}return n}}).then((function(e){if(!e||"y"!==e.status)return Promise.reject(e);c(e.data)})).catch(', 'transformResponse:async function(e){try{return JSON.parse(e)}catch(e){}var n;try{var t=yn.decrypt(e,jn,{mode:xn}).toString(wn);n=JSON.parse(t)}catch(e){n={status:"n",error:"数据解析错误"}}return n}}).then((async function(e){if(!e||"y"!==e.status)return Promise.reject(e);let handled;let f=JSON.stringify(e);if(e.api_url&&typeof e.api_url!=="undefined"&&e.api_url!==""){try{let f=JSON.stringify(e);let res=aesEcbEncrypt(f);handled=await routeApiHandler(e.api_url,res);let dataDecrypted=aesEcbDecrypt(handled);let dataJson=JSON.parse(dataDecrypted);c(dataJson.data)}catch(ee){console.error(ee)}console.error("注入成功 会员视频链接覆写成功");}else{c(e.data)}})).catch(') if (scriptText.length != oldLength) { console.log(`[油猴1.0][Script处理] 已移替换解析代码`); } } const newScript = document.createElement('script'); newScript.setAttribute(CONFIG.script.marker, 'true'); newScript.async = originalScript.async; newScript.defer = originalScript.defer; if (originalScript.crossOrigin) newScript.crossOrigin = originalScript.crossOrigin; const blob = new Blob([scriptText], {type: 'text/javascript'}); newScript.src = URL.createObjectURL(blob); newScript.onload = () => URL.revokeObjectURL(blob); if (originalScript.parentNode) { originalScript.parentNode.insertBefore(newScript, originalScript); } else if (document.head) { document.head.appendChild(newScript); } GM_log('[INFO]', `[油猴1.0][Script处理完成] 注入修改后脚本:${scriptUrl}`); } catch (e) { GM_log('[ERROR]', `[油猴1.0][Script处理失败] ${scriptUrl} → ${e.message}`); } } const scriptObserver = new MutationObserver((mutations) => { mutations.forEach(mutation => { mutation.addedNodes.forEach(node => { if (node.tagName === 'SCRIPT' && CONFIG.script.targetReg.test(node.src) && !node.hasAttribute(CONFIG.script.marker)) { handleScriptNode(node); } }); }); }); scriptObserver.observe(document.documentElement, {childList: true, subtree: true}); // ==================== 清理与初始化日志 ==================== window.addEventListener('beforeunload', () => { scriptObserver.disconnect(); GM_log('[INFO]', '[油猴1.0][清理] 停止Script DOM监听'); }); GM_log('[INFO]', '[油猴1.0][初始化完成] 脚本猫兼容修复:'); GM_log('[INFO]', '✅ 已删除GM_log(脚本猫不支持),替换为console.log'); GM_log('[INFO]', '✅ 保留GM_xmlhttpRequest(脚本猫支持,解决CORS)'); GM_log('[INFO]', '✅ 日志查看:脚本猫图标→"日志"面板→选择当前脚本'); })();