// ==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]', '✅ 日志查看:脚本猫图标→"日志"面板→选择当前脚本');
})();