Sleazy Fork is available in English.

sugar

sugar糖心解析

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

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