import {readFile} from 'fs/promises'; import {XMLHttpRequest} from 'xmlhttprequest'; import WebSocket, {WebSocketServer} from 'ws'; import path from "path"; import vm from 'vm'; import zlib from 'zlib'; import JSONbig from 'json-bigint'; import forge from "node-forge"; import * as minizlib from 'minizlib'; import * as utils from '../utils/utils.js'; import * as misc from '../utils/misc.js'; import COOKIE from '../utils/cookieManager.js'; import AIS from '../utils/ais.js'; import PanS from '../utils/pans.js'; import {createWebDAVClient} from '../utils/webdav.js'; import {createFTPClient} from '../utils/ftp.js'; import {ENV} from '../utils/env.js'; import {getContentType, getMimeType} from "../utils/mime-type.js"; import {getParsesDict, getSitesMap, pathLib, es6_extend_code, req_extend_code} from "../utils/file.js"; import {getFirstLetter} from "../utils/pinyin-tool.js"; import {reqs} from "../utils/req.js"; import {toBeijingTime} from "../utils/datetime-format.js" import "../utils/random-http-ua.js"; import {initializeGlobalDollar, rootRequire} from "../libs_drpy/moduleLoader.js"; import {base64Decode, base64Encode, md5, rc4, rc4_decode, rc4Decrypt, rc4Encrypt} from "../libs_drpy/crypto-util.js"; import template from '../libs_drpy/template.js' import batchExecute from '../libs_drpy/batchExecute.js'; import '../libs_drpy/drpyInject.js' import { MOBILE_UA, PC_UA, UA, UC_UA, IOS_UA, RULE_CK, CATE_EXCLUDE, TAB_EXCLUDE, OCR_RETRY, OCR_API, nodata, SPECIAL_URL, setResult, setHomeResult, setResult2, urlDeal, tellIsJx, urlencode, encodeUrl, uint8ArrayToBase64, Utf8ArrayToStr, gzip, ungzip, encodeStr, decodeStr, getCryptoJS, RSA, fixAdM3u8Ai, forceOrder, getQuery, dealJson, OcrApi, getHome, buildUrl, parseQueryString, buildQueryString, encodeIfContainsSpecialChars, objectToQueryString, getOriginalJs, pako, gbkTool, JSEncrypt, CryptoJS, NODERSA, JSON5, jinja, atob, btoa, stringify, lrcToSrt, strExtract, jsEncoder } from '../libs_drpy/drpyCustom.js'; import { pageRequestCache, cachedRequest, invokeWithInjectVars, homeParse, homeVodParse, cateParse, detailParse, searchParse, playParse, proxyParse, playParseAfter, detailParseAfter, cateParseAfter, searchParseAfter, homeParseAfter, commonClassParse, commonHomeListParse, commonCategoryListParse, commonDetailListParse, commonSearchListParse, commonLazyParse, executeJsCodeInSandbox, } from './drpysParser.js' import '../libs_drpy/es6-extend.js'; globalThis.JSONbig = JSONbig; // 抖音弹幕直播必须 globalThis._ENV = process.env; globalThis._fetch = fetch; globalThis.JsonBig = JSONbig({storeAsString: true}); globalThis.require = rootRequire; initializeGlobalDollar(); const {Ali, Baidu, Baidu2, Cloud, Pan, Quark, UC, Yun} = PanS; const { sleep, sleepSync, getNowTime, computeHash, deepCopy, urljoin, urljoin2, joinUrl, keysToLowerCase, naturalSort, $js, createBasicAuthHeaders, get_size, } = utils; // 缓存已初始化的模块和文件 hash 值 const moduleCache = new Map(); const ruleObjectCache = new Map(); const jxCache = new Map(); // 记录当前请求会话的标识,用于判断是否需要清理缓存 let currentSessionId = null; let lastClearTime = 0; // 记录当前会话是否已经清理过缓存 let sessionCacheCleared = false; // 记录各个会话的缓存清理状态,支持并发访问 const sessionCacheStates = new Map(); let pupWebview = null; if (typeof fetchByHiker === 'undefined') { // 判断是海阔直接放弃导入puppeteer try { // 尝试动态导入模块puppeteerHelper const {puppeteerHelper} = await import('../utils/headless-util.js'); // 使用动态 import pupWebview = new puppeteerHelper(); log('[getSandbox] puppeteerHelper imported successfully'); } catch (error) { // log('Failed to import puppeteerHelper:', error); log(`[getSandbox] Failed to import puppeteerHelper:${error.message}`); } } globalThis.pupWebview = pupWebview; try { if (typeof fetchByHiker !== 'undefined' && typeof globalThis.import === 'function') { await globalThis.import('../libs_drpy/crypto-js-wasm.js'); // 海阔放在globalThis里去动态引入 } else { await import('../libs_drpy/crypto-js-wasm.js'); // 使用动态 import规避海阔报错无法运行问题 } globalThis.CryptoJSW = CryptoJSWasm; } catch (error) { // log('Failed to import puppeteerHelper:', error); log(`[getSandbox] Failed to import CryptoJSWasm:${error.message}`); globalThis.CryptoJSW = { loadAllWasm: async function () { }, // MD5: async function (str) { // return md5(str) // }, ...CryptoJS }; } let simplecc = null; try { // 尝试动态导入模块puppeteerHelper const simWasm = await import('simplecc-wasm'); // 使用动态 import simplecc = simWasm.simplecc; log('[getSandbox] simplecc imported successfully'); } catch (error) { // log('Failed to import puppeteerHelper:', error); log(`[getSandbox] Failed to import simplecc:${error.message}`); } globalThis.simplecc = simplecc; let DataBase = null; let database = null; try { if (typeof fetchByHiker !== 'undefined' && typeof globalThis.import === 'function') { const sqliteUtil = await globalThis.import('../utils/database.js'); // 海阔放在globalThis里去动态引入 DataBase = sqliteUtil.DataBase; database = sqliteUtil.database; } else { const sqliteUtil = await import('../utils/database.js'); // 使用动态 import DataBase = sqliteUtil.DataBase; database = sqliteUtil.database; } log('[getSandbox] sqlite3 database imported successfully'); } catch (error) { log(`[getSandbox] Failed to import sqlite3:${error.message}`); } globalThis.DataBase = DataBase; globalThis.database = database; export async function getSandbox(env = {}) { const {getProxyUrl, requestHost, hostUrl, fServer} = env; // (可选) 加载所有 wasm 文件 await CryptoJSW.loadAllWasm(); const utilsSanbox = { sleep, sleepSync, utils, misc, computeHash, deepCopy, urljoin, urljoin2, joinUrl, naturalSort, $js, createBasicAuthHeaders, get_size, $, pupWebview, getProxyUrl, requestHost, hostUrl, fServer, getContentType, getMimeType, getParsesDict, getFirstLetter }; const drpySanbox = { jsp, pdfh, pd, pdfa, jsoup, pdfl, pjfh, pj, pjfa, pq, local, md5X, rsaX, aesX, desX, req, reqs, toBeijingTime, _fetch, XMLHttpRequest, simplecc, AIS, batchFetch, JSProxyStream, JSFile, js2Proxy, log, print, jsonToCookie, cookieToJson, runMain, cachedRequest, // 添加cachedRequest函数到沙箱中 }; const drpyCustomSanbox = { MOBILE_UA, PC_UA, UA, UC_UA, IOS_UA, RULE_CK, CATE_EXCLUDE, TAB_EXCLUDE, OCR_RETRY, OCR_API, nodata, SPECIAL_URL, setResult, setHomeResult, setResult2, urlDeal, tellIsJx, urlencode, encodeUrl, uint8ArrayToBase64, Utf8ArrayToStr, gzip, ungzip, encodeStr, decodeStr, getCryptoJS, RSA, fixAdM3u8Ai, forceOrder, getQuery, stringify, dealJson, OcrApi, getHome, buildUrl, keysToLowerCase, parseQueryString, buildQueryString, encodeIfContainsSpecialChars, objectToQueryString, forge, lrcToSrt, strExtract, }; const libsSanbox = { matchesAll, cut, gbkTool, CryptoJS, CryptoJSW, JSEncrypt, NODERSA, pako, JSON5, jinja, template, batchExecute, atob, btoa, base64Encode, base64Decode, md5, rc4Encrypt, rc4Decrypt, rc4, rc4_decode, randomUa, jsonpath, hlsParser, axios, axiosX, URL, pathLib, qs, Buffer, URLSearchParams, COOKIE, ENV, _ENV, Quark, Baidu, Baidu2, UC, Ali, Cloud, Yun, Pan, createWebDAVClient, createFTPClient, DataBase, database, require, WebSocket, WebSocketServer, zlib, JSONbig, JsonBig, minizlib, }; // 创建一个沙箱上下文,注入需要的全局变量和函数 const sandbox = { console, // 将 console 注入沙箱,便于调试 // eval, // 直接引入原生 eval(不要这样用,环境是隔离的会导致执行不符合预期,需要包装) WebAssembly, // 允许使用原生 WebAssembly(这里即使不引用也可以在沙箱里用这个变量。写在这里骗骗自己吧) setTimeout, // 注入定时器方法 setInterval, clearTimeout, clearInterval, TextEncoder, TextDecoder, performance, module: {}, // 模块支持 exports: {}, // 模块支持 rule: {}, // 用于存放导出的 rule 对象 jx: {},// 用于存放导出的 解析 对象 lazy: async function () { }, // 用于导出解析的默认函数 _asyncGetRule: null, _asyncGetLazy: null, ...utilsSanbox, ...drpySanbox, ...drpyCustomSanbox, ...libsSanbox, }; // 创建一个上下文 const context = vm.createContext(sandbox); // 注入扩展代码到沙箱中 const polyfillsScript = new vm.Script(es6_extend_code); polyfillsScript.runInContext(context); // 设置沙箱到全局 $ sandbox.$.setSandbox(sandbox); /* if (typeof fetchByHiker !== 'undefined') { // 临时解决海阔不支持eval问题,但是这个eval存在作用域问题,跟非海阔环境的有很大区别,属于残废版本 sandbox.eval = function (code) { const evalScript = new vm.Script(code); return evalScript.runInContext(context); }; } */ return { sandbox, context } } /** * 初始化模块:加载并执行模块文件,存储初始化后的 rule 对象 * 如果存在 `预处理` 属性且为函数,会在缓存前执行 * @param {string} filePath - 模块文件路径 * @param env * @param refresh 强制清除缓存 * @returns {Promise} - 返回初始化后的模块对象 */ export async function init(filePath, env = {}, refresh) { try { // 读取文件内容 const fileContent = await readFile(filePath, 'utf-8'); // 计算文件的 hash 值 const fileHash = computeHash(fileContent); const moduleName = path.basename(filePath, '.js'); const SitesMap = getSitesMap(); let moduleExt = env.ext || ''; // log('moduleName:', moduleName); // log('moduleExt:', moduleExt); // log('SitesMap:', SitesMap); if (moduleExt && SitesMap[moduleName]) { try { moduleExt = ungzip(moduleExt); } catch (e) { log(`[init] [${moduleName}] ungzip解密moduleExt失败: ${e.message}`); } if (!SitesMap[moduleName].find(i => i.queryStr === moduleExt) && !SitesMap[moduleName].find(i => i.queryObject.params === moduleExt)) { throw new Error("moduleExt is wrong!") } } let hashMd5 = md5(filePath + '#pAq#' + moduleExt); // 检查缓存:是否有文件且未刷新且文件 hash 未变化 if (moduleCache.has(hashMd5) && !refresh) { const cached = moduleCache.get(hashMd5); if (cached.hash === fileHash) { // log(`Module ${filePath} already initialized and unchanged, returning cached instance.`); return cached.moduleObject; } } log(`[init] Loading module: ${filePath}`); let t1 = getNowTime(); const {sandbox, context} = await getSandbox(env); // 执行文件内容,将其放入沙箱中 const js_code = await getOriginalJs(fileContent); // log('js_code:', js_code.slice(5000)); const js_code_wrapper = ` _asyncGetRule = (async function() { ${js_code} return rule; })(); `; const ruleScript = new vm.Script(js_code_wrapper); // ruleScript.runInContext(context); // const result = await ruleScript.runInContext(context); const executeWithTimeout = (script, context, timeout) => { return Promise.race([ new Promise((_, reject) => setTimeout(() => reject(new Error('Code execution timed out')), timeout) ), new Promise((resolve, reject) => { try { const result = script.runInContext(context); // 同步运行脚本 if (result && typeof result.then === 'function') { // 如果结果是 Promise,则等待其解析 result.then(resolve).catch(reject); } else { // 如果结果是非异步值,直接返回 resolve(result); } } catch (error) { reject(error); } }) ]); }; const result = await executeWithTimeout(ruleScript, context, 30000); // log('result:', result); // sandbox.rule = await sandbox._asyncGetRule; sandbox.rule = result; // rule注入完毕后添加自定义req扩展request方法进入规则,这个代码里可以直接获取rule的任意对象,而且还是独立隔离的 const reqExtendScript = new vm.Script(req_extend_code); reqExtendScript.runInContext(context); // 注意:不再直接挂载request/post函数到rule对象,避免内存占用和破坏沙箱隔离 // 解析函数将通过executeSandboxFunction在沙箱内调用request/post // 访问沙箱中的 rule 对象。不进行deepCopy了,避免初始化或者预处理对rule.xxx进行修改后,在其他函数里使用却没生效问题 // const moduleObject = deepCopy(sandbox.rule); const rule = sandbox.rule; if (moduleExt) { // 传了参数才覆盖rule参数,否则取rule内置 // log('moduleExt:', moduleExt); if (moduleExt.startsWith('../json')) { rule.params = urljoin(env.jsonUrl, moduleExt.slice(8)); } else { rule.params = moduleExt } } // 模板继承逻辑处理 await handleTemplateInheritance(rule, context); await initParse(rule, env, vm, context); // otherScript放入到initParse去执行 // const otherScript = new vm.Script(` // globalThis.jsp = new jsoup(rule.host||''); // globalThis.pdfh = pdfh; // globalThis.pd = pd; // globalThis.pdfa = pdfa; // globalThis.HOST = rule.host||''; // `); // otherScript.runInContext(context); let t2 = getNowTime(); // const moduleObject = deepCopy(rule); const moduleObject = rule; moduleObject.cost = t2 - t1; moduleObject.context = context; // 将沙箱上下文添加到moduleObject中 // log(`${filePath} headers:`, moduleObject.headers); // 清理原始rule对象以减少内存占用 // 由于已经深拷贝到moduleObject,原始rule不再需要 // delete sandbox.rule; // 缓存模块和文件的 hash 值 moduleCache.set(hashMd5, {moduleObject, hash: fileHash}); return moduleObject; } catch (error) { log(`[init] Error in drpy.init :${filePath}`, error); throw new Error(`[init] Failed to initialize module:${error.message}`); } } export async function getRuleObject(filePath, env, refresh) { try { // 读取文件内容 const fileContent = await readFile(filePath, 'utf-8'); // 计算文件的 hash 值 const fileHash = computeHash(fileContent); // 检查缓存:是否有文件且未刷新且文件 hash 未变化 if (ruleObjectCache.has(filePath) && !refresh) { const cached = ruleObjectCache.get(filePath); if (cached.hash === fileHash) { // log(`Module ${filePath} already initialized and unchanged, returning cached instance.`); return cached.ruleObject; } } // log(`Loading RuleObject: ${filePath} fileSize:${fileContent.length}`); let t1 = getNowTime(); const {sandbox, context} = await getSandbox(env); const js_code = await getOriginalJs(fileContent); const js_code_wrapper = ` _asyncGetRule = (async function() { ${js_code} return rule; })(); `; const ruleScript = new vm.Script(js_code_wrapper); ruleScript.runInContext(context); sandbox.rule = await sandbox._asyncGetRule; const rule = sandbox.rule; // 模板继承逻辑处理 await handleTemplateInheritance(rule, context); let t2 = getNowTime(); // const ruleObject = deepCopy(rule); const ruleObject = rule; // 设置可搜索、可筛选、可快搜等属性 ruleObject.searchable = ruleObject.hasOwnProperty('searchable') ? Number(ruleObject.searchable) : 0; ruleObject.filterable = ruleObject.hasOwnProperty('filterable') ? Number(ruleObject.filterable) : 0; ruleObject.quickSearch = ruleObject.hasOwnProperty('quickSearch') ? Number(ruleObject.quickSearch) : 0; ruleObject.cost = t2 - t1; // log(`${filePath} headers:`, moduleObject.headers); // 清理原始rule对象以减少内存占用 // 由于已经深拷贝到ruleObject,原始rule不再需要 // delete sandbox.rule; // 缓存模块和文件的 hash 值 ruleObjectCache.set(filePath, {ruleObject, hash: fileHash}); return ruleObject } catch (error) { log(`[getRuleObject] ${filePath} Error in drpy.getRuleObject:${error.message}`); return {} } } export async function initJx(filePath, env, refresh) { try { // 读取文件内容 const fileContent = await readFile(filePath, 'utf-8'); // 计算文件的 hash 值 const fileHash = computeHash(fileContent); // env一定传的object。这里只有两种情况 1: 获取配置的时候env传的空{} 2:实际解析传的真实环境,所以hash值只需要0和1 let hashMd5 = md5(filePath + '#pAq#' + (Object.keys(env).length === 0 ? 0 : 1)); // 检查缓存:是否有文件且未刷新且文件 hash 未变化 if (jxCache.has(hashMd5) && !refresh) { const cached = jxCache.get(hashMd5); if (cached.hash === fileHash) { // log(`Module ${filePath} already initialized and unchanged, returning cached instance.`); return cached.jxObj; } } log(`[initJx] Loading jx: ${filePath}, hash:${hashMd5}`); let t1 = getNowTime(); // log('env:', env); const {sandbox, context} = await getSandbox(env); // 执行文件内容,将其放入沙箱中 const js_code = await getOriginalJs(fileContent); const js_code_wrapper = ` _asyncGetLazy = (async function() { ${js_code} return {jx,lazy}; })(); `; const ruleScript = new vm.Script(js_code_wrapper); ruleScript.runInContext(context); const jxResult = await sandbox._asyncGetLazy; sandbox.lazy = jxResult.lazy; sandbox.jx = jxResult.jx; const reqExtendScript = new vm.Script(req_extend_code); reqExtendScript.runInContext(context); let t2 = getNowTime(); const jxObj = {...sandbox.jx, lazy: sandbox.lazy}; const cost = t2 - t1; log(`[initJx] 加载解析:${filePath} 耗时 ${cost}毫秒`) jxCache.set(hashMd5, {jxObj, hash: fileHash}); return jxObj; } catch (error) { log(`[initJx] Error in drpy.initJx:${filePath}`, error); throw new Error(`[initJx] Failed to initialize jx:${error.message}`); } } export async function isLoaded() { if (jxCache && jxCache.size > 0) { log('[isLoaded] Map 不为空,已完成初始化'); return true; } else { log('[isLoaded] Map 为空或未初始化'); return false; } } /** * 调用模块的指定方法 * @param {string} filePath - 模块文件路径 * @param env 全局的环境变量-针对本规则,如代理地址 * @param {string} method - 要调用的属性方法名称 * @param args - 传递给方法的普通参数 * @param {object} injectVars - 需要注入的变量(如 input 和 MY_URL) * @returns {Promise} - 方法调用的返回值 */ async function invokeMethod(filePath, env, method, args = [], injectVars = {}) { const moduleObject = await init(filePath, env); // 确保模块已初始化 switch (method) { case 'get_rule': return moduleObject; case 'class_parse': injectVars = await homeParse(moduleObject, ...args); if (!injectVars) { return {} } break case '推荐': injectVars = await homeVodParse(moduleObject, ...args); if (!injectVars) { return {} } break case '一级': injectVars = await cateParse(moduleObject, ...args); if (!injectVars) { return {} } break case '二级': injectVars = await detailParse(moduleObject, ...args); if (!injectVars) { return {} } break; case '搜索': injectVars = await searchParse(moduleObject, ...args); if (!injectVars) { return {} } break; case 'lazy': injectVars = await playParse(moduleObject, ...args); if (!injectVars) { return {} } break; case 'proxy_rule': injectVars = await proxyParse(moduleObject, ...args); if (!injectVars) { return {} } break; } injectVars['method'] = method; // 环境变量扩展进入this区域 Object.assign(injectVars, env); // 免嗅探代码特殊处理: 必须是函数或者没写 if (method === 'lazy' && ((moduleObject[method] && typeof moduleObject[method] === 'function') || !moduleObject[method])) { return await commonLazyParse(moduleObject, method, injectVars, args) } // 字符串lazy且非js:直接返回嗅探 else if (method === 'lazy' && typeof moduleObject[method] === 'string' && !moduleObject[method].startsWith('js:')) { return { parse: 1, url: injectVars.input, header: moduleObject.headers && Object.keys(moduleObject.headers).length > 0 ? moduleObject.headers : undefined } } // 分类动态解析特殊处理:允许不写 else if (method === 'class_parse' && !moduleObject[method]) { // 新增特性,可以不写class_parse属性 const tmpClassFunction = async function () { }; return await invokeWithInjectVars(moduleObject, tmpClassFunction, injectVars, args); } // 特殊处理class_parse字符串 else if (method === 'class_parse' && moduleObject[method] && typeof moduleObject[method] === 'string') { return await commonClassParse(moduleObject, method, injectVars, args); } // 函数直接执行 else if (moduleObject[method] && typeof moduleObject[method] === 'function') { // log('injectVars:', injectVars); return await invokeWithInjectVars(moduleObject, moduleObject[method], injectVars, args); } // 特殊处理js:开头的字符串,在沙箱环境中执行 else if (moduleObject[method] && typeof moduleObject[method] === 'string' && moduleObject[method].startsWith('js:')) { let result = await executeJsCodeInSandbox(moduleObject, method, injectVars, args); if (method === 'lazy') { result = await playParseAfter(moduleObject, result, args[1], args[0]); let ret_str = JSON.stringify(result); log(`[invokeMethod js:] 免嗅 ${injectVars.input} 执行完毕,结果为:`, ret_str.length < 100 ? ret_str : ret_str.slice(0, 100) + '...'); } else if (method === '二级') { result = await detailParseAfter(result); } else if (method === '一级') { result = await cateParseAfter(moduleObject, result, args[1]); log(`[invokeMethod js:] 一级 ${injectVars.input} 执行完毕,结果为:`, JSON.stringify(result.list.slice(0, 2))); } else if (method === '搜索') { result = await searchParseAfter(moduleObject, result, args[2]); log(`[invokeMethod js:] 搜索 ${injectVars.input} 执行完毕,结果为:`, JSON.stringify(result.list.slice(0, 2))); } else if (method === 'class_parse') { result = await homeParseAfter(result, moduleObject.类型, moduleObject.hikerListCol, moduleObject.hikerClassListCol, moduleObject.hikerSkipEr, injectVars); } return result; } // 特殊处理一级字符串 else if (method === '一级' && moduleObject[method] && typeof moduleObject[method] === 'string') { return await commonCategoryListParse(moduleObject, method, injectVars, args); } // 特殊处理搜索字符串 else if (method === '搜索' && moduleObject[method] && typeof moduleObject[method] === 'string') { return await commonSearchListParse(moduleObject, method, injectVars, args); } // 特殊处理推荐字符串 else if (method === '推荐' && moduleObject[method] && typeof moduleObject[method] === 'string') { return await commonHomeListParse(moduleObject, method, injectVars, args); } // 特殊处理二级字符串或对象 else if (method === '二级' && (moduleObject[method] === '*' || (moduleObject[method] && typeof moduleObject[method] === 'object'))) { return await commonDetailListParse(moduleObject, method, injectVars, args); } else { // 其他未知函数或者函数属性是字符串 if (['推荐', '一级', '搜索'].includes(method)) { return [] } else if (['二级'].includes(method)) { return {} } else { // class_parse一定要有,这样即使不返回数据都能自动取class_name和class_url的内容 throw new Error(`Method ${method} not found in module ${filePath}`); } } } async function initParse(rule, env, vm, context) { rule.host = (rule.host || '').rstrip('/'); // 检查并执行 `hostJs` 方法 if (typeof rule.hostJs === 'function') { log('[initParse] Executing hostJs...'); try { let HOST = await rule.hostJs.apply({input: rule.host, MY_URL: rule.host, HOST: rule.host}); if (HOST) { rule.host = HOST.rstrip('/'); log(`[initParse] 已动态设置规则【${rule.title}】的host为: ${rule.host}`); } } catch (e) { log(`[initParse] hostJs执行错误:${e.message}`); } } let rule_cate_excludes = (rule.cate_exclude || '').split('|').filter(it => it.trim()); let rule_tab_excludes = (rule.tab_exclude || '').split('|').filter(it => it.trim()); rule_cate_excludes = rule_cate_excludes.concat(CATE_EXCLUDE.split('|').filter(it => it.trim())); rule_tab_excludes = rule_tab_excludes.concat(TAB_EXCLUDE.split('|').filter(it => it.trim())); rule.cate_exclude = rule_cate_excludes.join('|'); rule.tab_exclude = rule_tab_excludes.join('|'); rule.类型 = rule.类型 || '影视'; // 影视|听书|漫画|小说 rule.url = rule.url || ''; rule.double = rule.double || false; rule.homeUrl = rule.homeUrl || ''; rule.detailUrl = rule.detailUrl || ''; rule.searchUrl = rule.searchUrl || ''; rule.homeUrl = rule.host && rule.homeUrl ? urljoin(rule.host, rule.homeUrl) : (rule.homeUrl || rule.host); rule.homeUrl = jinja.render(rule.homeUrl, {rule: rule}); rule.detailUrl = rule.host && rule.detailUrl ? urljoin(rule.host, rule.detailUrl) : rule.detailUrl; rule.二级访问前 = rule.二级访问前 || ''; if (rule.url.includes('[') && rule.url.includes(']')) { let u1 = rule.url.split('[')[0] let u2 = rule.url.split('[')[1].split(']')[0] rule.url = rule.host && rule.url ? urljoin(rule.host, u1) + '[' + urljoin(rule.host, u2) + ']' : rule.url; } else { rule.url = rule.host && rule.url ? urljoin(rule.host, rule.url) : rule.url; } if (rule.searchUrl.includes('[') && rule.searchUrl.includes(']') && !rule.searchUrl.includes('#')) { let u1 = rule.searchUrl.split('[')[0] let u2 = rule.searchUrl.split('[')[1].split(']')[0] rule.searchUrl = rule.host && rule.searchUrl ? urljoin(rule.host, u1) + '[' + urljoin(rule.host, u2) + ']' : rule.searchUrl; } else { rule.searchUrl = rule.host && rule.searchUrl ? urljoin(rule.host, rule.searchUrl) : rule.searchUrl; } rule.timeout = rule.timeout || 5000; rule.encoding = rule.编码 || rule.encoding || 'utf-8'; rule.search_encoding = rule.搜索编码 || rule.search_encoding || ''; rule.图片来源 = rule.图片来源 || ''; rule.图片替换 = rule.图片替换 || ''; rule.play_json = rule.hasOwnProperty('play_json') ? rule.play_json : []; rule.pagecount = rule.hasOwnProperty('pagecount') ? rule.pagecount : {}; rule.proxy_rule = rule.hasOwnProperty('proxy_rule') ? rule.proxy_rule : ''; if (!rule.hasOwnProperty('sniffer')) { // 默认关闭辅助嗅探 rule.sniffer = false; } // 二级为*自动添加hikerSkipEr属性允许跳过形式二级 if (!rule.hasOwnProperty('hikerSkipEr') && rule.二级 === '*') { rule.hikerSkipEr = 1; } rule.sniffer = rule.hasOwnProperty('sniffer') ? rule.sniffer : ''; rule.sniffer = !!(rule.sniffer && rule.sniffer !== '0' && rule.sniffer !== 'false'); rule.isVideo = rule.hasOwnProperty('isVideo') ? rule.isVideo : ''; if (rule.sniffer && !rule.isVideo) { // 默认辅助嗅探自动增强嗅探规则 rule.isVideo = 'http((?!http).){12,}?\\.(m3u8|mp4|flv|avi|mkv|rm|wmv|mpg|m4a|mp3)\\?.*|http((?!http).){12,}\\.(m3u8|mp4|flv|avi|mkv|rm|wmv|mpg|m4a|mp3)|http((?!http).)*?video/tos*|http((?!http).)*?obj/tos*'; } rule.tab_remove = rule.hasOwnProperty('tab_remove') ? rule.tab_remove : []; rule.tab_order = rule.hasOwnProperty('tab_order') ? rule.tab_order : []; rule.tab_rename = rule.hasOwnProperty('tab_rename') ? rule.tab_rename : {}; if (rule.headers && typeof (rule.headers) === 'object') { try { let header_keys = Object.keys(rule.headers); for (let k of header_keys) { if (k.toLowerCase() === 'user-agent') { let v = rule.headers[k]; if (['MOBILE_UA', 'PC_UA', 'UC_UA', 'IOS_UA', 'UA'].includes(v)) { rule.headers[k] = eval(v); log('[initParse]', rule.headers[k]) } } else if (k.toLowerCase() === 'cookie') { let v = rule.headers[k]; if (v && v.startsWith('http')) { try { // 直接请求,不使用缓存 v = await request(v, {headers: rule.headers || {}}); rule.headers[k] = v; } catch (e) { log(`[initParse] 从${v}获取cookie发生错误:${e.message}`); } } } } } catch (e) { log(`[initParse] 处理headers发生错误:${e.message}`); } } else { rule.headers = {} } // 新版放入规则内部 rule.oheaders = deepCopy(rule.headers); rule.rule_fetch_params = {'headers': rule.headers, 'timeout': rule.timeout, 'encoding': rule.encoding}; const originalScript = new vm.Script(` globalThis.oheaders = rule.oheaders globalThis.rule_fetch_params = rule.rule_fetch_params; `); originalScript.runInContext(context); // 检查并执行 `预处理` 方法 if (typeof rule.预处理 === 'function') { log('[initParse] Executing 预处理...'); await rule.预处理(env); } const otherScript = new vm.Script(` globalThis.jsp = new jsoup(rule.host||''); globalThis.pdfh = pdfh; globalThis.pd = pd; globalThis.pdfa = pdfa; globalThis.HOST = rule.host||''; `); otherScript.runInContext(context); return rule } export async function home(filePath, env, filter = 1) { // 只有在使用commonClassParse时才需要清理缓存 const moduleObject = await init(filePath, env); if (moduleObject.class_parse && typeof moduleObject.class_parse === 'string') { const sessionKey = md5(filePath + JSON.stringify(env)); clearPageRequestCache(sessionKey, 'home', moduleObject.host); } return await invokeMethod(filePath, env, 'class_parse', [filter], { input: '$.homeUrl', MY_URL: '$.homeUrl' }); } export async function homeVod(filePath, env) { // 只有在使用commonHomeListParse时才需要清理缓存 const moduleObject = await init(filePath, env); if (moduleObject['推荐'] && typeof moduleObject['推荐'] === 'string') { const sessionKey = md5(filePath + JSON.stringify(env)); clearPageRequestCache(sessionKey, 'homeVod', moduleObject.host); } return await invokeMethod(filePath, env, '推荐', [], { input: '$.homeUrl', MY_URL: '$.homeUrl' }); } export async function category(filePath, env, tid, pg = 1, filter = 1, extend = {}) { // category函数不使用缓存,无需清理 return await invokeMethod(filePath, env, '一级', [tid, pg, filter, extend], { input: '$.url', MY_URL: '$.url' }); } export async function detail(filePath, env, ids) { // detail函数不使用缓存,无需清理 if (!Array.isArray(ids)) throw new Error('Parameter "ids" must be an array'); return await invokeMethod(filePath, env, '二级', [ids], { input: `${ids[0]}`, MY_URL: `${ids[0]}` }); } export async function search(filePath, env, wd, quick = 0, pg = 1) { // search函数不使用缓存,无需清理 return await invokeMethod(filePath, env, '搜索', [wd, quick, pg], { input: '$.searchUrl', MY_URL: '$.searchUrl' }); } export async function play(filePath, env, flag, id, flags) { flags = flags || []; if (!Array.isArray(flags)) throw new Error('Parameter "flags" must be an array'); return await invokeMethod(filePath, env, 'lazy', [flag, id, flags], { input: `${id}`, MY_URL: `${id}`, }); } export async function proxy(filePath, env, params) { params = params || {}; try { return await invokeMethod(filePath, env, 'proxy_rule', [deepCopy(params)], { input: `${params.url}`, MY_URL: `${params.url}`, }); } catch (e) { return [500, 'text/plain', '代理规则错误:' + e.message] } } export async function action(filePath, env, action, value) { try { return await invokeMethod(filePath, env, 'action', [action, value], {}); } catch (e) { return '动作规则错误:' + e.message } } export async function getRule(filePath, env) { return await invokeMethod(filePath, env, 'get_rule', [], {}); } export async function jx(filePath, env, params) { params = params || {}; try { const jxObj = await initJx(filePath, env); // 确保模块已初始化 const lazy = await jxObj.lazy; const result = await lazy(params.url || '', params); // log(`[jx]: ${JSON.stringify(result)}`); return result; } catch (e) { return {code: 404, url: '', msg: `${filePath} 代理解析错误:${e.message}`, cost: ''} } } export async function getJx(filePath) { try { // 确保模块已初始化 const jxObj = await initJx(filePath, {}); // log('jxObj:', jxObj); return jxObj; } catch (e) { return {code: 403, error: `${filePath} 获取代理信息错误:${e.message}`} } } /** * 执行main函数 * 示例 function main(text){return gzip(text)} * @param main_func_code * @param arg */ export async function runMain(main_func_code, arg) { let mainFunc = async function () { return '' }; try { eval(main_func_code + '\nmainFunc=main;'); return mainFunc(arg); } catch (e) { log(`[runMain] 执行main_func_code发生了错误:${e.message}`); return '' } } // 清理缓存函数 export function clearAllCache() { const excludeList = ['APP模板配置']; let clearedCount = 0; // 清理moduleCache,跳过排除列表中的模块 for (const [key, value] of moduleCache.entries()) { let shouldSkip = false; // 检查是否在排除列表中 for (const excludeName of excludeList) { if (value.moduleObject && value.moduleObject.title && value.moduleObject.title.includes(excludeName)) { log(`[clearAllCache] 跳过清理模块缓存: ${value.moduleObject.title}`); shouldSkip = true; break; } } if (!shouldSkip) { moduleCache.delete(key); clearedCount++; } } // 清理ruleObjectCache,跳过排除列表中的模块 for (const [filePath, value] of ruleObjectCache.entries()) { let shouldSkip = false; for (const excludeName of excludeList) { if (filePath.includes(excludeName)) { log(`[clearAllCache] 跳过清理规则缓存: ${filePath}`); shouldSkip = true; break; } } if (!shouldSkip) { ruleObjectCache.delete(filePath); clearedCount++; } } // 清理jxCache,跳过排除列表中的模块 for (const [key, value] of jxCache.entries()) { let shouldSkip = false; for (const excludeName of excludeList) { if (key.includes(excludeName)) { log(`[clearAllCache] 跳过清理解析缓存: ${key}`); shouldSkip = true; break; } } if (!shouldSkip) { jxCache.delete(key); clearedCount++; } } // 清理页面请求缓存 pageRequestCache.clear(); log('[clearAllCache] 已清理页面请求缓存'); // 重置会话状态 currentSessionId = null; sessionCacheCleared = false; sessionCacheStates.clear(); log('[clearAllCache] 重置会话状态'); log(`[clearAllCache] 已清理 ${clearedCount} 个模块缓存,排除了 ${excludeList.join(', ')} 相关缓存`); } /** * 清理页面请求缓存 * 按sessionKey和host清理对应的缓存项,支持多个并发会话 * @param {string} sessionKey - 会话键(基于filePath和env的MD5) * @param {string} sessionType - 会话类型(home/homeVod等) * @param {string} host - 主机地址,用于区分不同host的缓存 */ function clearPageRequestCache(sessionKey = null, sessionType = 'unknown', host = '') { if (!sessionKey) { pageRequestCache.clear(); sessionCacheStates.clear(); sessionCacheCleared = false; log('[clearPageRequestCache] 强制清理所有缓存'); return; } // 检查当前会话是否已清理过缓存 const sessionCacheKey = `${sessionKey}:${host}`; const isSessionCleared = sessionCacheStates.get(sessionCacheKey); if (!isSessionCleared) { // 清理属于当前host的缓存项 const keysToDelete = []; for (const [cacheKey] of pageRequestCache) { // 缓存键格式: host:md5(url+options) // 只清理匹配当前host的缓存项 if (cacheKey.startsWith(`${host}:`)) { keysToDelete.push(cacheKey); } } // 清理缓存项 keysToDelete.forEach(key => pageRequestCache.delete(key)); // 标记当前会话已清理 sessionCacheStates.set(sessionCacheKey, true); currentSessionId = sessionKey; sessionCacheCleared = true; lastClearTime = Date.now(); log(`[clearPageRequestCache] ${sessionType}会话开始,清理${keysToDelete.length}个${host}的缓存项: ${sessionKey}`); } else { // log(`[clearPageRequestCache] ${sessionType}会话复用${host}的缓存: ${sessionKey}`); } } /** * 处理模板继承逻辑 * @param {Object} rule - 规则对象 * @param {Object} context - VM上下文 */ async function handleTemplateInheritance(rule, context) { try { // 获取模板字典 const muban = template.getMubans(); // 处理自动模板匹配 if (rule['模板'] === '自动') { try { let host_headers = rule['headers'] || {}; const cacheKey = md5(rule.host + JSON.stringify(host_headers)); // 使用cachedRequest统一缓存管理,避免重复缓存 log(`[handleTemplateInheritance] 请求HOST页面: ${rule.host}`); // 使用通用沙箱函数执行cachedRequest const script = new vm.Script(` (async function() { try { return await cachedRequest(request, '${rule.host}', ${JSON.stringify({headers: host_headers})}, 'host'); } catch (e) { log('[handleTemplateInheritance] 获取HOST页面失败:', e.message); return ''; } })() `); let host_html = await script.runInContext(context); let match_muban = ''; let muban_keys = Object.keys(muban).filter(it => !/默认|短视2|采集1/.test(it)); for (let muban_key of muban_keys) { try { // 检查模板是否有class_parse if (muban[muban_key].class_parse) { let class_parse = muban[muban_key].class_parse; // 直接在context中执行class_parse代码进行测试 const testScript = new vm.Script(` (async function() { try { let html = ${JSON.stringify(host_html)}; let p = ${JSON.stringify(class_parse)}; let classes = []; // 处理class_parse字符串解析 p = p.split(';'); let p0 = p[0]; let is_json = p0.startsWith('json:'); p0 = p0.replace(/^(jsp:|json:|jq:)/, ''); if (html) { let $pdfa, $pdfh, $pd; if (is_json) { html = dealJson(html); $pdfa = pjfa; $pdfh = pjfh; $pd = pj; } else { $pdfa = pdfa; $pdfh = pdfh; $pd = pd; } if (is_json) { try { let list = $pdfa(html, p0); if (list && list.length > 0) { classes = list; } } catch (e) { log('[handleTemplateInheritance] json分类解析失败:' + e.message); } } else if (p.length >= 3) { try { let list = $pdfa(html, p0); if (list && list.length > 0) { for (const it of list) { try { //log('[handleTemplateInheritance] test it:',it); let name = $pdfh(it, p[1]); let url = $pd(it, p[2]); if (p.length > 3 && p[3]) { let exp = new RegExp(p[3]); let match = url.match(exp); if (match && match[1]) { url = match[1]; } } if (name.trim()) { classes.push({ 'type_id': url.trim(), 'type_name': name.trim() }); } } catch (e) { log('[handleTemplateInheritance] 分类列表解析元素失败:' + e.message); } } } } catch (e) { log('[handleTemplateInheritance] 分类列表解析失败:' + e.message); } } } return { class: classes }; } catch (e) { log('[handleTemplateInheritance] 模板测试执行错误:', e.message); return { class: [] }; } })() `); const host_data = await testScript.runInContext(context); if (host_data.class && host_data.class.length > 0) { match_muban = muban_key; log(`[handleTemplateInheritance] 自动匹配模板:【${muban_key}】`); break; } } } catch (e) { log(`[handleTemplateInheritance] 自动匹配模板:【${muban_key}】错误:${e.message}`); } } if (match_muban) { muban['自动'] = muban[match_muban]; } else { delete rule['模板']; } } catch (e) { log('[handleTemplateInheritance] 自动模板匹配失败:', e.message); delete rule['模板']; } } // 处理模板修改 - 在模板继承之前统一执行,避免重复执行 if (rule['模板修改'] && typeof rule['模板修改'] === 'function' && rule.模板 && muban.hasOwnProperty(rule.模板)) { try { // 将muban传入函数,让模板修改能够修改模板 await rule['模板修改'](muban); } catch (e) { log('[handleTemplateInheritance] 模板修改执行失败:', e.message); } } // 处理普通模板继承 if (rule.模板 && muban.hasOwnProperty(rule.模板)) { log('[handleTemplateInheritance] 继承模板:' + rule.模板); // 使用Object.assign进行模板继承,模板属性在前,rule属性在后(rule属性优先级更高) const templateRule = muban[rule.模板]; const originalRule = {...rule}; Object.assign(rule, templateRule, originalRule); } // 清理模板相关属性 delete rule['模板']; delete rule['模板修改']; } catch (error) { log('[handleTemplateInheritance] 模板继承处理失败:', error.message); } } export {jsEncoder}